/*--------------------------------------------------------------*/
/* vesta.c ---                                                  */
/*                                                              */
/*      This file reads two files:  (1) A verilog netlist file  */
/*      of a circuit (structural verilog, that is, gate-level   */
/*      information only), and (2) a liberty format file        */
/*      containing timing information for the standard cell     */
/*      macros instantiated in the verilog netlist.  In         */
/*      addition, it may take an optional third file containing */
/*      information about the resistance and capacitance of the */
/*      wiring (see below).                                     */
/*                                                              */
/*      Options are supplied as command-line arguments:         */
/*                                                              */
/*              -d <delay_file> Wiring delays (see below)       */
/*              -p <value>      Clock period, in ps             */
/*              -l <value>      Output load, in fF              */
/*              -v <level>      set verbose mode                */
/*              -V              report version number           */
/*		-L 		Long format (print paths)	*/
/*              -e              exhaustive search               */
/*                                                              */
/*      Currently the only output this tool generates is a      */
/*      list of paths with negative slack.  If no paths have    */
/*      negative slack, then the 20 paths with the smallest     */
/*      positive slack are output, following a "success"        */
/*      message.  If no clock period is supplied, then the      */
/*      clock period is set to equal the delay of the longest   */
/*      delay path, and the 20 paths with the smallest positive */
/*      slack are output, following a statement indicated the   */
/*      computed minimum clock period.                          */
/*--------------------------------------------------------------*/
/*	(c) 2013-2017 Tim Edwards, Open Circuit Design		*/
/*	Released under GPL as part of the qflow package		*/
/*--------------------------------------------------------------*/

/*--------------------------------------------------------------*/
/*      Wiring delay file:                                      */
/*      For qflow, the wiring delay is generated by the tool    */
/*      "def2delays".  The file format is as follows:           */
/*                                                              */
/*      <net_name>                                              */
/*      <output_terminal>  <net_capacitance>                    */
/*      <input_terminal_1> <delay_1>                            */
/*      ...                                                     */
/*      <input_terminal_N> <delay_N>                            */
/*                                                              */
/*      -<net_capacitance> is in fF                             */
/*      -Values <delay_i> are in ps                             */
/*      -<input_terminal_N> line *must* be following by a blank */
/*       line                                                   */
/*--------------------------------------------------------------*/

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <errno.h>
#include <stdarg.h>
#include <math.h>       // Temporary, for fabs()
#include "hash.h"       // For net hash table

#define LIB_LINE_MAX  65535

#ifdef __APPLE__
// Linux defines a comparison function prototype, the Mac doesn't. . .
typedef int (*__compar_fn_t)(const void *, const void *);
#endif

int fileCurrentLine;

// Analysis types --- note that maximum flop-to-flop delay
// requires calculating minimum clock skew time, and vice
// versa, so it is necessary that these have TRUE/FALSE
// values.

#define MINIMUM_TIME    0
#define MAXIMUM_TIME    1

// Multiple-use definition
#define UNKNOWN         -1

// Sections of liberty file
#define INIT            0
#define LIBBLOCK        1
#define CELLDEF         2
#define PINDEF          3
#define FLOPDEF         4
#define LATCHDEF        5
#define TIMING          6

// Sections of verilog file
#define MODULE          0
#define IOLIST          1
#define GATELIST        2
#define INSTANCE        3
#define INSTPIN         4
#define PINCONN         5

// Pin types (these are masks---e.g., a pin can be an INPUT and a CLOCK)
#define INPUT           0x01    // The default
#define OUTPUT          0x02
#define DFFCLK          0x04
#define DFFIN           0x08    // Flop input
#define DFFOUT          0x10    // Flop output
#define DFFSET          0x20    // Flop set (preset)
#define DFFRST          0x40    // Flop reset (clear)
#define LATCHIN         0x80    // Latch input
#define LATCHEN         0x100   // Latch enable
#define LATCHOUT        0x200   // Latch output

// Timing type (for tables)

#define TIMING_PROP_TRANS       0
#define TIMING_HOLD             1
#define TIMING_SETUP            2
#define TIMING_SET_RESET        3
#define TIMING_RECOVERY         4
#define TIMING_REMOVAL          5
#define TIMING_TRISTATE         6

// A few short-hand definitions
#define DFF_ALL_IN      (DFFCLK | DFFIN | DFFSET | DFFRST)
#define LATCH_ALL_IN    (LATCHIN | LATCHEN)

#define DFF_IN_NOT_CLK  (DFFIN | DFFSET | DFFRST)
#define LATCH_IN_NOT_EN (LATCHIN)

#define REGISTER_IN     (DFF_ALL_IN | LATCH_ALL_IN)
#define REG_IN_NOT_CLK  (DFF_IN_NOT_CLK | LATCH_IN_NOT_EN)

#define IOMASK          0x03
#define DFFMASK         0x7c
#define LATCHMASK       0x180

// Pin sense
#define SENSE_UNKNOWN   0       // Sense unknown
#define SENSE_NONE      1       // Non-unate
#define SENSE_POSITIVE  2       // Positive-unate
#define SENSE_NEGATIVE  3       // Negative-unate

// Signal transition direction
#define EDGE_UNKNOWN    0
#define RISING          1
#define FALLING         2
#define EITHER          3

// Function translation
#define GROUPBEGIN      1
#define GROUPEND        2
#define SIGNAL          3
#define OPERATOR        4
#define XOPERATOR       5
#define SEPARATOR       6

// Net types
#define NET             0x00    // Ordinary net (default)
#define CLOCK           0x01    // Clock net (path start)
#define OUTTERM         0x02    // Module output
#define ASYNC           0x04    // Asynchronous set/reset
#define TERMINAL        0x08    // DFF input (path terminal)
#define LATCHTERM       0x10    // Latch input (dependent path terminal)
#define ENABLE          0x20    // Latch enable (path start)

// Cell types
#define GATE            0x00    // Combinatorial gate (default)
#define DFF             0x01    // Flip-flop (shared bit field)
#define CLK_SENSE_MASK  0x02    // Clock edge mask (0=positive, 1=negative)
#define RST_MASK        0x04    // Reset mask (0=no reset, 1=reset)
#define RST_SENSE_MASK  0x08    // Reset edge mask (0=positive, 1=negative)
#define SET_MASK        0x10    // Set mask (0=no set, 1=set)
#define SET_SENSE_MASK  0x20    // Set edge mask (0=positive, 1=negative)
#define LATCH           0x40    // Latch type
#define EN_SENSE_MASK   0x80    // Latch enable edge mask (0=positive, 1=negative)

// Some names for cell types based on masks

#define DFFCP           0x01    // Pos clock
#define DFFCN           0x03    // Neg clock
#define DFFCPRP         0x05    // Pos clock, Pos reset
#define DFFCNRP         0x07    // Neg clock, Pos reset
#define DFFCPRN         0x0d    // Pos clock, Neg reset
#define DFFCNRN         0x0f    // Neg clock, Neg reset
#define DFFCPSP         0x11    // Pos clock, Pos set
#define DFFCNSP         0x13    // Neg clock, Pos set
#define DFFCPRPSP       0x15    // Pos clock, Pos reset, Pos set
#define DFFCNRPSP       0x17    // Neg clock, Pos reset, Pos set
#define DFFCPRNSP       0x1d    // Pos clock, Neg reset, Pos set
#define DFFCNRNSP       0x1f    // Neg clock, Neg reset, Pos set
#define DFFCPSN         0x31    // Pos clock, Neg set
#define DFFCNSN         0x33    // Neg clock, Neg set
#define DFFCPRPSN       0x35    // Pos clock, Pos reset, Neg set
#define DFFCNRPSN       0x37    // Neg clock, Pos reset, Neg set
#define DFFCPRNSN       0x3d    // Pos clock, Neg reset, Neg set
#define DFFCNRNSN       0x3f    // Neg clock, Neg reset, Neg set

/*--------------------------------------------------------------*/
/* Liberty file database                                        */
/*--------------------------------------------------------------*/

// Types of array

// Array type 1:  Propagation time
#define OUTPUT_CAP              0
#define TRANSITION_TIME         1

// Array type 2:  Setup, hold, recovery, removal times
#define RELATED_TIME            2
#define CONSTRAINED_TIME        3

typedef struct _lutable *lutableptr;

typedef struct _lutable {
    char *name;
    char invert;        // 0 if times x caps, 1 if caps x times
    int  var1;          // Type of array in index1
    int  var2;          // Type of array in index2
    int  size1;         // Number of entries in time array
    int  size2;         // Number of entries in cap (or constrained timing) array
    union {
        double *times;  // Time array (units ps)
        double *rel;    // Related pin transition time array (units ps)
    } idx1;
    union {
        double *caps;   // Cap array (units fF)
        double *cons;   // Constrained pin transition time array (units ps)
    } idx2;
    double *values;     // Matrix of values (used locally, not for templates)
    lutableptr next;
} lutable;

typedef struct _pin *pinptr;
typedef struct _cell *cellptr;

typedef struct _pin {
    char *name;
    short type;

    double capr;        // Capacitance for rising input
    double capf;        // Capacitance for falling input (optional)

    short sense;        // Sense (positive-unate, negative-unate, non-unate)
    lutable *propdelr;  // Reference table for rising output prop delay relative to driver
    lutable *propdelf;  // Reference table for falling output prop delay relative to driver
    lutable *transr;    // Reference table for transition rise time
    lutable *transf;    // Reference table for transition fall time

    cellptr refcell;    // Pointer back to parent cell

    pinptr next;
} pin;

typedef struct _cell {
    short type;
    char *name;
    char *function;
    pin  *pins;         /* List of input pins with timing info */
    double area;
    double maxtrans;    /* Maximum transition time */
    double maxcap;      /* Maximum allowable load */
    cellptr next;
} cell;

/*--------------------------------------------------------------*/
/* Verilog netlist database                                     */
/*--------------------------------------------------------------*/

typedef struct _net *netptr;
typedef struct _connect *connptr;
typedef struct _delaydata *ddataptr;

typedef struct _net {
   char *name;
   connptr driver;
   short type;
   int fanout;
   connptr *receivers;
   double loadr;        /* Total load capacitance for rising input */
   double loadf;        /* Total load capacitance for falling input */
   netptr next;
} net;

typedef struct _instance *instptr;

typedef struct _connect {
   double   metric;             /* Delay metric at connection */
   double   icDelay;            /* interconnect delay in ps */
   instptr  refinst;
   pinptr   refpin;
   netptr   refnet;
   ddataptr tag;                /* Tag value for checking for loops and endpoints */
   double   *prvector;          /* Prop delay rising (at load condition) vector */
   double   *pfvector;          /* Prop delay falling (at load condition) vector */
   double   *trvector;          /* Transition time rising (at load condition) vector */
   double   *tfvector;          /* Transition time falling (at load condition) vector */
   connptr  next;
} connect;

typedef struct _instance {
   char *name;
   cellptr refcell;
   connptr in_connects;
   connptr out_connects;
   instptr next;
} instance;

// Linked list of delays (backtrace to source)

typedef struct _btdata *btptr;

typedef struct _btdata {
   double  delay;       /* Propagation delay to this point */
   double  trans;       /* Transition time at this point */
   short   dir;         /* Edge direction at this point */
   connptr receiver;    /* Receiver connection at end of path */
   int     refcnt;      /* Reference counter for backtrace data */
   btptr   next;        /* Path of propagation */
} btdata;

// Linked list of backtrace records

typedef struct _delaydata {
   double delay;        /* Total delay, including setup and clock skew */
   double trans;        /* Transition time at destination, used to find setup */
   btptr backtrace;
   ddataptr  next;
} delaydata;

// Linked list of connection pointers
// (Much like delaydata, but without all the timing information)

typedef struct _connlist *connlistptr;

typedef struct _connlist {
   connptr connection;
   connlistptr next;
} connlist;

/* Global variables */

unsigned char verbose;          /* Level of debug output generated */
unsigned char exhaustive;       /* Exhaustive search mode */

/*--------------------------------------------------------------*/
/* Grab a token from the input                                  */
/* Return the token, or NULL if we have reached end-of-file.    */
/*--------------------------------------------------------------*/

char *
advancetoken(FILE *flib, char delimiter)
{
    static char token[LIB_LINE_MAX];
    static char line[LIB_LINE_MAX];
    static char *linepos = NULL;

    char *lineptr = linepos;
    char *lptr, *tptr;
    char *result;
    int commentblock, concat, nest;

    commentblock = 0;
    concat = 0;
    nest = 0;
    while (1) {         /* Keep processing until we get a token or hit EOF */

        if (lineptr != NULL && *lineptr == '/' && *(lineptr + 1) == '*') {
            commentblock = 1;
        }

        if (commentblock == 1) {
            if ((lptr = strstr(lineptr, "*/")) != NULL) {
                lineptr = lptr + 2;
                commentblock = 0;
            }
            else lineptr = NULL;
        }

        if (lineptr == NULL || *lineptr == '\n' || *lineptr == '\0') {
            result = fgets(line, LIB_LINE_MAX, flib);
            fileCurrentLine++;
            if (result == NULL) return NULL;

            /* Keep pulling stuff in if the line ends with a continuation character */
            lptr = line;
            while (*lptr != '\n' && *lptr != '\0') {
                if (*lptr == '\\') {
                    // To be considered a line continuation marker, there must be
                    // only whitespace or newline between the backslash and the
                    // end of the string.
                    char *eptr = lptr + 1;
                    while (isspace(*eptr)) eptr++;
                    if (*eptr == '\0') {
                        result = fgets(lptr, LIB_LINE_MAX - (lptr - line), flib);
                        fileCurrentLine++;
                        if (result == NULL) break;
                    }
                    else
                        lptr++;
                }
                else
                    lptr++;
            }
            if (result == NULL) return NULL;
            lineptr = line;
        }

        if (commentblock == 1) continue;

        while (isspace(*lineptr)) lineptr++;
        if (concat == 0)
            tptr = token;

        // Find the next token and return just the token.  Update linepos
        // to the position just beyond the token.  All delimiters like
        // parentheses, quotes, etc., are returned as single tokens

        // If delimiter is declared, then we stop when we reach the
        // delimiter character, and return all the text preceding it
        // as the token.  If delimiter is 0, then we look for standard
        // delimiters, and separate them out and return them as tokens
        // if found.

        while (1) {
            if (*lineptr == '\n' || *lineptr == '\0')
                break;
            if (*lineptr == '/' && *(lineptr + 1) == '*')
                break;
            if (delimiter != 0 && *lineptr == delimiter) {
                if (nest > 0)
                    nest--;
                else
                    break;
            }

            // Watch for nested delimiters!
            if (delimiter == '}' && *lineptr == '{') nest++;
            if (delimiter == ')' && *lineptr == '(') nest++;

            if (delimiter == 0)
                if (*lineptr == ' ' || *lineptr == '\t')
                    break;

            if (delimiter == 0) {
                if (*lineptr == '(' || *lineptr == ')') {
                    if (tptr == token) *tptr++ = *lineptr++;
                    break;
                }
                if (*lineptr == '{' || *lineptr == '}') {
                    if (tptr == token) *tptr++ = *lineptr++;
                    break;
                }
                if (*lineptr == '\"' || *lineptr == ':' || *lineptr == ';') {
                    if (tptr == token) *tptr++ = *lineptr++;
                    break;
                }
            }

            *tptr++ = *lineptr++;
        }
        *tptr = '\0';
        if ((delimiter != 0) && (*lineptr != delimiter))
            concat = 1;
        else if ((delimiter != 0) && (*lineptr == delimiter))
            break;
        else if (tptr > token)
            break;
    }
    if (delimiter != 0) lineptr++;

    while (isspace(*lineptr)) lineptr++;
    linepos = lineptr;

    // Final:  Remove trailing whitespace
    tptr = token + strlen(token) - 1;
    while (isspace(*tptr)) {
        *tptr = '\0';
        tptr--;
    }
    return token;
}

/*--------------------------------------------------------------*/
/* Parse a pin name.  Check if the cell has a pin of that name, */
/* and if not, add the pin to the cell, giving it default       */
/* values.  The pin name may contain quotes, parentheses, or    */
/* negations ("!" or "'");  these should be ignored.            */
/*--------------------------------------------------------------*/

pinptr parse_pin(cellptr newcell, char *token, short sense_predef)
{
    pinptr newpin, lastpin;
    char *pinname, *sptr;

    // Advance to first legal pin name character

    pinname = token;
    while (isspace(*pinname) || (*pinname == '\'') || (*pinname == '\"') ||
                (*pinname == '!') || (*pinname == '(') || (*pinname == ')'))
        pinname++;

    sptr = pinname;
    while (*sptr != '\0') {
        if (isspace(*sptr) || (*sptr == '\'') || (*sptr == '\"') ||
                (*sptr == '!') || (*sptr == '(') || (*sptr == ')')) {
            *sptr = '\0';
            break;
        }
        sptr++;
    }

    // Check if pin was already defined

    lastpin = NULL;
    newpin = newcell->pins;
    while (newpin) {
        lastpin = newpin;
        if (!strcmp(newpin->name, pinname))
            return newpin;
        newpin = newpin->next;
    }

    // Pin was not defined, so create a new one and add it to the cell
    // at the end of the cell's pin list.

    newpin = (pin *)malloc(sizeof(pin));
    newpin->name = strdup(pinname);
    newpin->next = NULL;

    if (lastpin != NULL)
        lastpin->next = newpin;
    else
        newcell->pins = newpin;

    newpin->type = INPUT;       // The default; modified later, if not an input
    newpin->capr = 0.0;
    newpin->capf = 0.0;
    newpin->sense = sense_predef;  // Again, modified later if not true.
    newpin->propdelr = NULL;
    newpin->propdelf = NULL;
    newpin->transr = NULL;
    newpin->transf = NULL;
    newpin->refcell = newcell;  // Create link back to cell
    return newpin;
}

/*--------------------------------------------------------------*/
/* Create a new net record                                      */
/*--------------------------------------------------------------*/

netptr create_net(netptr *netlist) {

    netptr newnet;

    newnet = (netptr)malloc(sizeof(net));
    newnet->name = NULL;
    newnet->next = *netlist;
    *netlist = newnet;
    newnet->driver = NULL;
    newnet->fanout = 0;
    newnet->receivers = NULL;
    newnet->loadr = 0.0;
    newnet->loadf = 0.0;
    newnet->type = NET;

    return newnet;
}

/*----------------------------------------------------------------------*/
/* Interpolate or extrapolate a vector from a time vs. capacitance      */
/* lookup table.                                                        */
/*----------------------------------------------------------------------*/

double *table_collapse(lutableptr tableptr, double load)
{
    double *vector;
    double cfrac, vlow, vhigh;
    int i, j;

    vector = (double *)malloc(tableptr->size1 * sizeof(double));

    // If the table is 1-dimensional, then just return a copy of the table.
    if (tableptr->size2 <= 1) {
       for (i = 0; i < tableptr->size1; i++) {
          *(vector + i) = *(tableptr->values + i);
       }
       return vector;
    }

    // Find cap load index entries bounding  "load", or the two nearest
    // entries, if extrapolating

    if (load < tableptr->idx2.caps[0])
        j = 1;
    else if (load >= tableptr->idx2.caps[tableptr->size2 - 1])
        j = tableptr->size2 - 1;
    else {
        for (j = 0; j < tableptr->size2; j++)
            if (tableptr->idx2.caps[j] > load)
                break;
    }

    cfrac = (load - tableptr->idx2.caps[j - 1]) /
                        (tableptr->idx2.caps[j] - tableptr->idx2.caps[j - 1]);

    for (i = 0; i < tableptr->size1; i++) {

        // Interpolate value at cap load for each transition value

        vlow = *(tableptr->values + i * tableptr->size1 + (j - 1));
        vhigh = *(tableptr->values + i * tableptr->size1 + j);
        *(vector + i) = vlow + (vhigh - vlow) * cfrac;
    }
    return vector;
}

/*----------------------------------------------------------------------*/
/* Interpolate/extrapolate a delay or transition value from a vector of */
/* values at a known output load.  The original full 2D table contains  */
/* the transition time index values.                                    */
/*----------------------------------------------------------------------*/

double vector_get_value(lutableptr tableptr, double *vector, double trans)
{
    int i;
    double tfrac, vlow, vhigh, value;

    // Find time index entries bounding  "trans", or the two nearest
    // entries, if extrapolating

    if (trans < tableptr->idx1.times[0])
        i = 1;
    else if (trans >= tableptr->idx1.times[tableptr->size1 - 1])
        i = tableptr->size1 - 1;
    else {
        for (i = 0; i < tableptr->size1; i++)
            if (tableptr->idx1.times[i] > trans)
                break;
    }

    // Compute transition time as a fraction of the nearest table indexes
    // for transition times

    tfrac = (trans - tableptr->idx1.times[i - 1]) /
                (tableptr->idx1.times[i] - tableptr->idx1.times[i - 1]);

    // Interpolate value
    vlow = *(vector + (i - 1));
    vhigh = *(vector + i);
    value = vlow + (vhigh - vlow) * tfrac;
    return value;
}

/*----------------------------------------------------------------------*/
/* Interpolate or extrapolate a value from a related time vs.           */
/* constrained time lookup table.                                       */
/*----------------------------------------------------------------------*/

double binomial_get_value(lutableptr tableptr, double rtrans, double ctrans)
{
    int i, j;
    double rfrac, cfrac, vlow, vhigh, valuel, valueh, value;

    /* Tables have been arranged such that idx1 is related time,        */
    /* idx2 is constrained time */

    // Find time index entries bounding  "rtrans", or the two nearest
    // entries, if extrapolating

    if (rtrans < tableptr->idx1.rel[0])
        i = 1;
    else if (rtrans >= tableptr->idx1.rel[tableptr->size1 - 1])
        i = tableptr->size1 - 1;
    else {
        for (i = 0; i < tableptr->size1; i++)
            if (tableptr->idx1.rel[i] > rtrans)
                break;
    }

    // Compute transition time as a fraction of the nearest table indexes
    // for transition times

    rfrac = (rtrans - tableptr->idx1.rel[i - 1]) /
                (tableptr->idx1.rel[i] - tableptr->idx1.rel[i - 1]);

    // 1-dimensional computation, if this table is 1-dimensional

    if (tableptr->size2 == 0) {
        vlow = *(tableptr->values + (i - 1));
        vhigh = *(tableptr->values + i);
        value = vlow + (vhigh - vlow) * rfrac;
        return value;
    }

    // Find cons index entries bounding  "ctrans", or the two nearest
    // entries, if extrapolating

    if (ctrans < tableptr->idx2.cons[0])
        j = 1;
    else if (ctrans >= tableptr->idx2.cons[tableptr->size2 - 1])
        j = tableptr->size2 - 1;
    else {
        for (j = 0; j < tableptr->size2; j++)
            if (tableptr->idx2.cons[j] > ctrans)
                break;
    }

    // Compute cons transition as a fraction of the nearest table indexes for cons

    cfrac = (ctrans - tableptr->idx2.cons[j - 1]) /
                        (tableptr->idx2.cons[j] - tableptr->idx2.cons[j - 1]);

    // Interpolate value at cons lower bound
    vlow = *(tableptr->values + (i - 1) * tableptr->size1 + (j - 1));
    vhigh = *(tableptr->values + i * tableptr->size1 + (j - 1));
    valuel = vlow + (vhigh - vlow) * rfrac;

    // Interpolate value at cons upper bound
    vlow = *(tableptr->values + (i - 1) * tableptr->size1 + j);
    vhigh = *(tableptr->values + i * tableptr->size1 + j);
    valueh = vlow + (vhigh - vlow) * rfrac;

    // Final interpolation (binomial interpolation)
    value = valuel + (valueh - valuel) * cfrac;
    return value;
}

/*----------------------------------------------------------------------*/
/* Determine how the sense of a signal changes going from a gate's      */
/* input to its output.  If the gate's input pin is positive unate      */
/* relative to the gate output, then the signal sense remains the same. */
/* If it is negative unate, then the signal sense inverts.  If it is    */
/* non-unate, then the signal sense becomes non-unate, and we calculate */
/* timing for both edges from that point forward, always accepting the  */
/* maximum time.                                                        */
/*----------------------------------------------------------------------*/

short calc_dir(pinptr testpin, short dir)
{
    short outdir;

    outdir = UNKNOWN;
    if (testpin == NULL) return dir;

    switch(dir) {
        case RISING:
            if (testpin->sense == SENSE_POSITIVE)
                outdir = RISING;        /* rising input, rising output */
            else if (testpin->sense == SENSE_NEGATIVE)
                outdir = FALLING;       /* rising input, falling output */
            else
                outdir = EITHER;        /* output can be rising or falling */
            break;
        case FALLING:
            if (testpin->sense == SENSE_POSITIVE)
                outdir = FALLING;       /* falling input, falling output */
            else if (testpin->sense == SENSE_NEGATIVE)
                outdir = RISING;        /* falling input, rising output */
            else
                outdir = EITHER;                /* output can be rising or falling */
            break;
        case EITHER:
            outdir = EITHER;            /* output can be rising or falling */
            break;
    }
    return outdir;
}

/*----------------------------------------------------------------------*/
/* Calculate the propagation delay from "testpin" to the output         */
/* of the gate to which "testpin" is an input.                          */
/*                                                                      */
/* "sense" is a pointer to the sense at the input.  SENSE_POSITIVE      */
/* indicates a rising edge at the pin, SENSE_NEGATIVE indicates a       */
/* falling edge at the pin.  "sense" is updated to indicate if the      */
/* output transition is rising, falling, or unknown (SENSE_NONE).       */
/*                                                                      */
/* "loadnet" is a pointer to the net connected to the cell instance's   */
/* output pin.  Load values will be taken from this net, depending on   */
/* the sense of the output.                                             */
/*                                                                      */
/* "testpin" is the pin receiving the input signal, and the pin record  */
/* containing the relevant timing tables.                               */
/*----------------------------------------------------------------------*/

double calc_prop_delay(double trans, connptr testconn, short sense, char minmax)
{
    pinptr testpin;
    double propdelayr, propdelayf;

    propdelayr = 0.0;
    propdelayf = 0.0;

    testpin = testconn->refpin;
    if (testpin == NULL) return 0.0;

    if (sense != SENSE_NEGATIVE) {
        if (testconn->prvector)
            propdelayr = vector_get_value(testpin->propdelr, testconn->prvector, trans);
        if (sense == SENSE_POSITIVE) return propdelayr;
    }

    if (sense != SENSE_POSITIVE) {
        if (testconn->pfvector)
            propdelayf = vector_get_value(testpin->propdelf, testconn->pfvector, trans);
        if (sense == SENSE_NEGATIVE) return propdelayf;
    }

    if (minmax == MAXIMUM_TIME)
        return (propdelayr > propdelayf) ? propdelayr : propdelayf;
    else
        return (propdelayr < propdelayf) ? propdelayr : propdelayf;
}

/*----------------------------------------------------------------------*/
/* Calculate the transition time from "testpin" to the output           */
/* of the gate to which "testpin" is an input.  This is equivalent to   */
/* the propagation delay calculation routine above, apart from using    */
/* the lookup tables for transition time instead of propagation delay.  */
/*----------------------------------------------------------------------*/

double calc_transition(double trans, connptr testconn, short sense, char minmax)
{
    pinptr testpin;
    double transr, transf;

    testpin = testconn->refpin;
    if (testpin == NULL) return 0.0;

    transr = 0.0;
    transf = 0.0;

    if (sense != SENSE_NEGATIVE) {
        if (testconn->trvector)
            transr = vector_get_value(testpin->transr, testconn->trvector, trans);
        if (sense == SENSE_POSITIVE) return transr;
    }

    if (sense != SENSE_POSITIVE) {
        if (testconn->tfvector)
            transf = vector_get_value(testpin->transf, testconn->tfvector, trans);
        if (sense == SENSE_NEGATIVE) return transf;
    }

    if (minmax == MAXIMUM_TIME)
        return (transr > transf) ? transr : transf;
    else
        return (transr < transf) ? transr : transf;
}

/*----------------------------------------------------------------------*/
/* Calculate the hold time for a flop input "testpin" relative to the   */
/* flop clock, where "trans" is the transition time of the signal at    */
/* "testpin", and "clktrans" is the transition time of the clock        */
/* signal at the clock pin.  "sense" is the sense of the input signal   */
/* at "testpin".                                                        */
/*----------------------------------------------------------------------*/

double calc_hold_time(double trans, pinptr testpin, double clktrans, short sense,
                char minmax)
{
    double holdr, holdf;

    if (testpin == NULL) return 0.0;

    holdr = 0.0;
    holdf = 0.0;

    if (sense != SENSE_NEGATIVE) {
        if (testpin->transr)
            holdr = binomial_get_value(testpin->transr, trans, clktrans);
        if (sense == SENSE_POSITIVE) return holdr;
    }

    if (sense != SENSE_POSITIVE) {
        if (testpin->transf)
            holdf = binomial_get_value(testpin->transf, trans, clktrans);
        if (sense == SENSE_NEGATIVE) return holdf;
    }

    if (minmax == MAXIMUM_TIME)
        return (holdr > holdf) ? holdr : holdf;
    else
        return (holdr < holdf) ? holdr : holdf;
}

/*----------------------------------------------------------------------*/
/* Calculate the setup time for a flop input "testpin" relative to the  */
/* flop clock, where "trans" is the transition time of the signal at    */
/* "testpin", and "clktrans" is the transition time of the clock        */
/* signal at the clock pin.  "sense" is the sense of the input signal   */
/* at "testpin".                                                        */
/*----------------------------------------------------------------------*/

double calc_setup_time(double trans, pinptr testpin, double clktrans, short sense,
                char minmax)
{
    double setupr, setupf;

    if (testpin == NULL) return 0.0;

    setupr = 0.0;
    setupf = 0.0;

    if (sense != SENSE_NEGATIVE) {
        if (testpin->propdelr)
            setupr = binomial_get_value(testpin->propdelr, trans, clktrans);
        if (sense == SENSE_POSITIVE) return setupr;
    }

    if (sense != SENSE_POSITIVE) {
        if (testpin->propdelf)
            setupf = binomial_get_value(testpin->propdelf, trans, clktrans);
        if (sense == SENSE_NEGATIVE) return setupf;
    }

    if (minmax == MAXIMUM_TIME)
        return (setupr > setupf) ? setupr : setupf;
    else
        return (setupr < setupf) ? setupr : setupf;
}

/*--------------------------------------------------------------*/
/* Find the path from a clock back to all inputs or flop        */
/* outputs.  This list will be used to find nodes that are      */
/* common to other clocks.                                      */
/*--------------------------------------------------------------*/

void
find_clock_source(connptr testlink, btptr *clocklist, short dir)
{
    netptr clknet;
    connptr driver, iinput;
    instptr iupstream;
    btptr newclock;
    short newdir;

    /* Add this connection record to clocklist */

    newclock = (btptr)malloc(sizeof(btdata));
    newclock->delay = 0.0;
    newclock->trans = 0.0;
    newclock->dir = dir;
    newclock->refcnt = 1;
    newclock->receiver = testlink;
    newclock->next = *clocklist;
    *clocklist = newclock;

    clknet = testlink->refnet;
    driver = clknet->driver;
    if (driver == NULL) return;                 /* Reached a module input */
    iupstream = driver->refinst;

    if (iupstream == NULL) return;              /* Not supposed to happen? */
    if (driver->refpin->type & DFFOUT) return;  /* Reached a flop output */
    if (driver->refpin->type & LATCHOUT) return; /* Reached a latch output */

    for (iinput = iupstream->in_connects; iinput; iinput = iinput->next) {
        newdir = calc_dir(iinput->refpin, dir);
        find_clock_source(iinput, clocklist, newdir);
    }
}

/*--------------------------------------------------------------*/
/* Find a net that is common to both "clocklist" and            */
/* "clock2list".  If one exists, return a pointer to the        */
/* connection.                                                  */
/*--------------------------------------------------------------*/

netptr find_common_clock(btptr clocklist, btptr clock2list)
{
    btptr srch1ptr, srch2ptr;

    for (srch1ptr = clocklist; srch1ptr; srch1ptr = srch1ptr->next)
        for (srch2ptr = clock2list; srch2ptr; srch2ptr = srch2ptr->next)
            if (srch1ptr->receiver->refnet == srch2ptr->receiver->refnet)
                return srch1ptr->receiver->refnet;

    return NULL;
}

/*--------------------------------------------------------------*/
/* Determine the delay to a clock pin from the farthest point   */
/* back in the network, either to an input pin or the output of */
/* another flop (e.g., a ripple counter).  If there is more     */
/* than one such source (which can happen with, say, a gated    */
/* clock, because this routine will not differentiate between   */
/* the clock signal and the gating signal), then all sources    */
/* recorded (it is only necessary to find a common root of all  */
/* other related clocks downstream).                            */
/*                                                              */
/* This is a recursive routine, continuing to find all delays   */
/* through the circuit until it reaches "terminal".  The        */
/* "delaylist" linked list is not modified by this routine.     */
/*--------------------------------------------------------------*/

void
find_clock_delay(int dir, double delay, double trans, connptr receiver,
                btptr clocklist, connptr terminal, char minmax) {

    pinptr  testpin;
    netptr  loadnet;
    cellptr testcell;
    instptr testinst;
    btptr   testbtdata, newbtdata;
    double  newdelayr, newdelayf, newtransr, newtransf;
    short   outdir;
    int     i;

    testpin = receiver->refpin;

    // Stop when receiver matches terminal.

    if (receiver != terminal) {

        testinst = receiver->refinst;
        testcell = (testpin) ? testpin->refcell : NULL;

        // Don't follow signal through any DFF pins
        if (testcell && (testcell->type & DFF)) return;

        // Compute delay from gate input to output

        outdir = calc_dir(testpin, dir);
        if (outdir & RISING) {
            newdelayr = delay + calc_prop_delay(trans, receiver, RISING, minmax);
            newtransr = calc_transition(trans, receiver, RISING, minmax);
        }
        if (outdir & FALLING) {
            newdelayf = delay + calc_prop_delay(trans, receiver, FALLING, minmax);
            newtransf = calc_transition(trans, receiver, FALLING, minmax);
        }

        loadnet = (testinst) ? testinst->out_connects->refnet : NULL;
        if (loadnet != NULL) {
            for (i = 0; i < loadnet->fanout; i++) {
                if (outdir & RISING)
                    find_clock_delay(RISING, newdelayr, newtransr, loadnet->receivers[i],
                                clocklist, terminal, minmax);
                if (outdir & FALLING)
                    find_clock_delay(FALLING, newdelayf, newtransf, loadnet->receivers[i],
                                clocklist, terminal, minmax);
            }
        }
    }
    else {

        /* Determine if receiver is in clocklist */
        for (testbtdata = clocklist; testbtdata; testbtdata = testbtdata->next) {
            if (testbtdata->receiver == receiver) {
                /* Is delay greater than that already recorded?  If so, replace it */
                if (minmax == MAXIMUM_TIME) {
                    if (delay > testbtdata->delay) {
                        testbtdata->delay = delay;
                        testbtdata->trans = trans;
                        testbtdata->dir = dir;
                    }
                }
                else {
                    if (delay < testbtdata->delay) {
                        testbtdata->delay = delay;
                        testbtdata->trans = trans;
                        testbtdata->dir = dir;
                    }
                }
                break;
            }
        }
    }
}

/*--------------------------------------------------------------*/
/* Determine the delay from input to output through a gate      */
/*                                                              */
/* This is a recursive routine, continuing to find all delays   */
/* through the circuit until it reaches a terminal or flop      */
/* input.  It is similar to find_clock_delay, but stops on all  */
/* terminal points found in the path, rather than stopping on   */
/* a specific connection.                                       */
/*                                                              */
/* Also unlike find_clock_delay, the routine keeps a running    */
/* record of the path followed from the source, as a character  */
/* string.  When a terminal is found, the path and delay are    */
/* saved and added to "delaylist".  After the recursive search, */
/* "delaylist" contains a list of all paths starting from the   */
/* original connection "receiver" and ending on a clock or an   */
/* output pin.  Where multiple paths exist between source and   */
/* destination, only the path with the longest delay is kept.   */
/*                                                              */
/* Return the number of new paths recorded.                     */
/*--------------------------------------------------------------*/

int find_path_delay(int dir, double delay, double trans, connptr receiver,
                btptr backtrace, ddataptr *delaylist, char minmax) {

    pinptr   testpin;
    netptr   loadnet;
    cellptr  testcell;
    instptr  testinst;
    btptr    newbtdata, freebt, testbt;
    ddataptr testddata, newddata;
    double   newdelayr, newdelayf, newtransr, newtransf;
    short    outdir;
    char     replace;
    int      i, numpaths;

    numpaths = 0;
    testpin = receiver->refpin;

    // Prevent exhaustive search by stopping on a metric.  Note that the
    // nonlinear table-based delay data requires an exhaustive search;
    // generally, the tables can be assumed to be monotonic, in which case
    // we can stop if the delay is less than the greatest delay recorded
    // at this point AND the transition time is less than the transition
    // time recorded along with that delay.  A more relaxed metric is to
    // use the delay plus the transition time, and an even more relaxed
    // metric is to use only the delay.  Any relaxing of the metric
    // implies that the final result may not be the absolute maximum delay,
    // although it will typically vary by less than an average gate delay.

    if (!exhaustive) {
        if (minmax == MAXIMUM_TIME) {
            if (delay <= receiver->metric)
                return numpaths;
        }
        else {
            if (delay >= receiver->metric)
                return numpaths;
        }
    }

    // Check for a logic loop, and truncate the path to avoid infinite
    // looping in the path search.

    if (receiver->tag == (ddataptr)(-1)) return numpaths;
    else if (receiver->tag == NULL) receiver->tag = (ddataptr)(-1);

    // Record this position and delay/transition information

    newbtdata = (btptr)malloc(sizeof(btdata));
    newbtdata->receiver = receiver;
    newbtdata->delay = delay + newbtdata->receiver->icDelay;
    newbtdata->trans = trans;
    newbtdata->dir = dir;
    newbtdata->refcnt = 1;
    newbtdata->next = backtrace;

    // Stop when we hit a module output pin or any flop/latch input.
    // We must allow the routine to pass through the 1st register clock (on the first
    // time through, backtrace is NULL).

    if ((backtrace == NULL) || (testpin && ((testpin->type & REGISTER_IN) == 0))) {

        testinst = receiver->refinst;
        testcell = (testpin) ? testpin->refcell : NULL;

        // Compute delay from gate input to output

        outdir = calc_dir(testpin, dir);
        if (outdir & RISING) {
            newdelayr = delay + calc_prop_delay(trans, receiver, RISING, minmax);
            newtransr = calc_transition(trans, receiver, RISING, minmax);
        }
        if (outdir & FALLING) {
            newdelayf = delay + calc_prop_delay(trans, receiver, FALLING, minmax);
            newtransf = calc_transition(trans, receiver, FALLING, minmax);
        }

        loadnet = (testinst) ? testinst->out_connects->refnet : receiver->refnet;
        for (i = 0; i < loadnet->fanout; i++) {
            if (outdir & RISING)
                numpaths += find_path_delay(RISING, newdelayr, newtransr,
                        loadnet->receivers[i], newbtdata, delaylist, minmax);
            if (outdir & FALLING)
                numpaths += find_path_delay(FALLING, newdelayf, newtransf,
                        loadnet->receivers[i], newbtdata, delaylist, minmax);
        }
        receiver->tag = NULL;
    }
    else {

        /* Is receiver already in delaylist? */
        if ((receiver->tag != (ddataptr)(-1)) && (receiver->tag != NULL)) {

            /* Position in delaylist is recorded in tag field */
            testddata = receiver->tag;

            if (testddata->backtrace->receiver == receiver) {
                replace = 0;
                if (minmax == MAXIMUM_TIME) {
                    /* Is delay greater than that already recorded?  If so, replace it */
                    if (delay > testddata->backtrace->delay)
                        replace = 1;
                }
                else {
                    /* Is delay less than that already recorded?  If so, replace it */
                    if (delay < testddata->backtrace->delay)
                        replace = 1;
                }
                if (replace) {

                    /* Remove the existing path record and replace it */
                    while (testddata->backtrace != NULL) {
                        freebt = testddata->backtrace;
                        testddata->backtrace = testddata->backtrace->next;
                        freebt->refcnt--;
                        if (freebt->refcnt <= 0) free(freebt);
                    }
                    testddata->backtrace = newbtdata;

                    /* Increment the refcounts along the backtrace */
                    for (testbt = newbtdata; testbt; testbt = testbt->next)
                        testbt->refcnt++;
                }
            }
            else
                fprintf(stderr, "ERROR:  Bad endpoint tag!\n");
        }
        else
            testddata = NULL;

        // If we have found a propagation path from source to dest,
        // record it in delaylist.

        if (testddata == NULL) {
            numpaths++;
            newddata = (ddataptr)malloc(sizeof(delaydata));
            newddata->delay = 0.0;
            newddata->trans = 0.0;
            newddata->backtrace = newbtdata;
            newddata->next = *delaylist;
            *delaylist = newddata;

            /* Mark the receiver as having been visited */
            receiver->tag = *delaylist;

            /* Increment the refcounts along the backtrace */
            for (testbt = newbtdata; testbt; testbt = testbt->next)
                testbt->refcnt++;
        }

    }

    receiver->metric = delay;
    newbtdata->refcnt--;
    if (newbtdata->refcnt <= 0) free(newbtdata);
    return numpaths;
}

/*--------------------------------------------------------------*/
/* Search the list "clocklist" for all points that are module   */
/* inputs or flop outputs, and compute the worst-case           */
/* transition time downstream at testlink.                      */
/*                                                              */
/* Return a pointer to the ddataptr entry that contains the     */
/* worst-case transition time.                                  */
/*--------------------------------------------------------------*/

btptr find_clock_transition(btptr clocklist, connptr testlink, short dir, char minmax)
{
    btptr testclock, testlinkptr, resetclock;
    connptr testconn;
    double tdriver;

    // Find out where testlink is in clocklist, and save the position.

    for (testclock = clocklist; testclock; testclock = testclock->next) {
        if (testclock->receiver == testlink) {
            testlinkptr = testclock;
            break;
        }
    }
    if (testclock == NULL) return NULL; // Error---testlink wasn't in clocklist!

    for (testclock = clocklist; testclock; testclock = testclock->next) {
        testconn = testclock->receiver;
        tdriver = 0.0;          // to-do:  set to default input transition time
        find_clock_delay(testlinkptr->dir, 0.0, tdriver, testconn, clocklist, testlink,
                        minmax);
    }

    // Return the linkptr containing the recorded transition time from
    // source to destination clock pins

    return testlinkptr;
}

/*--------------------------------------------------------------*/
/* Given an instance record, find the pin of the instance that  */
/* is the clock, if the instance is a flop.  If the instance is */
/* not a flop, return NULL.                                     */
/*--------------------------------------------------------------*/

connptr find_register_clock(instptr testinst)
{
    connptr testconn;

    for (testconn = testinst->in_connects; testconn; testconn = testconn->next)
        if (testconn->refpin && (testconn->refpin->type & DFFCLK))
            return testconn;

    return NULL;
}

/*--------------------------------------------------------------*/
/* Given an edge direction (RISING or FALLING) at a source net, */
/* and given a destination net, find the sense of the signal    */
/* when it arrives at the destination net.                      */
/*--------------------------------------------------------------*/

short find_edge_dir(short dir, netptr sourcenet, netptr destnet) {
    int i;
    short outdir, rdir;
    connptr testconn, nextconn;
    instptr testinst;
    netptr nextnet;

    for (i = 0; i < sourcenet->fanout; i++) {
        testconn = sourcenet->receivers[i];
        testinst = testconn->refinst;
        if (testinst == NULL) continue;
        if (testconn->refpin == NULL) continue;
        if ((testconn->refpin->type & REGISTER_IN) != 0) continue;
        nextconn = testinst->out_connects;
        nextnet  = nextconn->refnet;
        outdir = calc_dir(testconn->refpin, dir);
        if (nextnet == destnet) return outdir;

        rdir = find_edge_dir(outdir, nextnet, destnet);
        if (rdir != 0) return rdir;
    }
    return 0;
}

/*--------------------------------------------------------------*/
/* Search all paths from the clocked data outputs of            */
/* "clockedlist" to either output pins or data inputs of other  */
/* flops.                                                       */
/*                                                              */
/* Return a master list of all backtraces in "masterlist".      */
/*                                                              */
/* Return value is the number of paths recorded in masterlist.  */
/*                                                              */
/* If minmax == MAXIMUM_TIME, return the maximum delay.         */
/* If minmax == MINIMUM_TIME, return the minimum delay.         */
/*--------------------------------------------------------------*/

int find_clock_to_term_paths(connlistptr clockedlist, ddataptr *masterlist, netptr netlist,
                char minmax)
{
    netptr      commonclock, testnet;
    connptr     testconn, thisconn;
    connlistptr testlink;
    pinptr      testpin;
    cellptr     testcell;
    instptr     testinst;
    btptr       clocklist, clock2list, backtrace, freebt;
    btptr       selectedsource, selecteddest;
    ddataptr    delaylist, testddata;
    ddataptr    freeddata;

    short       srcdir, destdir;                // Signal direction in/out
    double      tdriver, setupdelay, holddelay;
    char        clk_sense_inv, clk_invert;
    int         numpaths, n, i;

    delaylist = NULL;
    clocklist = NULL;
    clock2list = NULL;

    numpaths = 0;
    for (testlink = clockedlist; testlink; testlink = testlink->next) {

        // Remove all tags and reset delay metrics before each run

        for (testnet = netlist; testnet; testnet = testnet->next) {
            for (i = 0; i < testnet->fanout; i++) {
                testconn = testnet->receivers[i];
                testconn->tag = NULL;
                if (minmax == MAXIMUM_TIME)
                    testconn->metric = -1.0;
                else
                    testconn->metric = 1E50;
            }
        }

        thisconn = testlink->connection;
        testpin = thisconn->refpin;
        if (testpin) {
            testcell = testpin->refcell;

            // Sense is positive for rising edge-triggered flops, negative for
            // falling edge-triggered flops
            srcdir = (testcell->type & CLK_SENSE_MASK) ? FALLING : RISING;

            // Find the sources of the clock at the path start
            find_clock_source(thisconn, &clocklist, srcdir);

            // Find the clock source with the worst-case transition time at testlink
            // (Note:  For maximum path delay, find minimum clock transistion, and vice versa)
            selectedsource = find_clock_transition(clocklist, thisconn, srcdir, ~minmax);
            if (selectedsource == NULL)
                tdriver = 0.0;
            else
                tdriver = selectedsource->trans;

            // Report on paths and their maximum delays
            if (verbose > 0)
                fprintf(stdout, "Paths starting at flop \"%s\" clock:\n\n",
                                thisconn->refinst->name);

        }
        else {
            // Connection is an input pin;  must calculate both rising and falling edges.
            srcdir = EITHER;
            tdriver = 0.0;      // To-do: use designated input transition time

            // Report on paths and their maximum delays
            if (verbose > 0)
                fprintf(stdout, "Paths starting at input pin \"%s\"\n\n",
                                thisconn->refnet->name);
        }

        if (verbose > 0) fflush(stdout);

        // Find all paths from "thisconn" to output or a flop input, and compute delay
        n = find_path_delay(srcdir, 0.0, tdriver, thisconn, NULL, &delaylist, minmax);
        numpaths += n;

        if (verbose > 0) fprintf(stdout, "%d paths traced (%d total).\n\n", n, numpaths);

        for (testddata = delaylist; testddata; testddata = testddata->next) {
            // Copy last backtrace delay to testddata.
            testddata->delay = testddata->backtrace->delay;
            testddata->trans = testddata->backtrace->trans;
            testinst = testddata->backtrace->receiver->refinst;

            if (testinst != NULL) {
                // Find the sources of the clock at the path end
                destdir = (testinst->refcell->type & CLK_SENSE_MASK) ? FALLING : RISING;
                testconn = find_register_clock(testinst);
                // If testconn is NULL, this is not a register (latch, maybe?)
                if (testconn == NULL) continue;
                find_clock_source(testconn, &clock2list, destdir);
                selecteddest = find_clock_transition(clock2list, testconn, destdir, ~minmax);

                // Find the connection that is common to both clocks
                commonclock = find_common_clock(clocklist, clock2list);
                if (commonclock == NULL) {
                    // Warn about asynchronous clock sources
                    if (verbose > 0) {
                        fflush(stdout);
                        fprintf(stderr, "Independent clock nets \"%s\" and \"%s\""
                                " drive related gates!\n",
                                testconn->refnet->name, thisconn->refnet->name);
                    }
                    clk_invert = -1;
                }
                else {
                    // Add or subtract difference in arrival times between source and
                    // destination clocks

                    if (selecteddest != NULL && selectedsource != NULL) {
                        testddata->delay += selecteddest->delay;
                        testddata->delay -= selectedsource->delay;

                        /* Check if the clock signal arrives at both flops with the     */
                        /* same edge type (both rising or both falling).                */

                        clk_invert = (find_edge_dir(RISING, commonclock,
                                                selectedsource->receiver->refnet) ==
                                        find_edge_dir(RISING, commonclock,
                                                selecteddest->receiver->refnet)) ? 0 : 1;
                    }

                    if (minmax == MAXIMUM_TIME) {
                        // Add setup time for destination clocks
                        setupdelay = calc_setup_time(testddata->trans,
                                        testddata->backtrace->receiver->refpin,
                                        selecteddest->trans,
                                        testddata->backtrace->dir, minmax);
                        testddata->delay += setupdelay;
                    }
                    else {
                        // Subtract hold time for destination clocks
                        holddelay = calc_hold_time(testddata->trans,
                                        testddata->backtrace->receiver->refpin,
                                        selecteddest->trans,
                                        testddata->backtrace->dir, minmax);
                        testddata->delay -= holddelay;
                    }

                    if (verbose > 0)
                        fprintf(stdout, "Path terminated on flop \"%s\" input with max delay %g ps\n",
                                testconn->refinst->name, testddata->delay);

                    for (backtrace = testddata->backtrace; backtrace->next;
                                backtrace = backtrace->next) {
                        if (verbose > 0)
                            fprintf(stdout, "   %g (%s) %s/%s -> %s/%s\n",
                                        backtrace->delay,
                                        backtrace->receiver->refnet->name,
                                        backtrace->receiver->refnet->driver->refinst->name,
                                        backtrace->receiver->refnet->driver->refpin->name,
                                        backtrace->receiver->refinst->name,
                                        backtrace->receiver->refpin->name);
                    }
                    if (verbose > 0)
                        fprintf(stdout, "   000.000 (%s) %s/%s -> %s/%s\n",
                                backtrace->receiver->refnet->name,
                                backtrace->receiver->refinst->name,
                                backtrace->receiver->refpin->name,
                                backtrace->receiver->refinst->name,
                                backtrace->receiver->refinst->out_connects->refpin->name);

                    if (selecteddest != NULL && selectedsource != NULL) {
                        if (verbose > 0) {
                            if (selectedsource->receiver->refnet != selecteddest->receiver->refnet) {
                                fprintf(stdout, "   %g %s to %s clock skew\n",
                                        selecteddest->delay - selectedsource->delay,
                                        selectedsource->receiver->refnet->name,
                                        selecteddest->receiver->refnet->name);
                            }
                        }

                        /* Check if the flops have the same clock sense */
                        /* (both are clock rising edge or both are clock falling edge type) */

                        if ((testinst->refcell->type & CLK_SENSE_MASK) !=
                                        (backtrace->receiver->refinst->refcell->type
                                        & CLK_SENSE_MASK))
                            clk_sense_inv = 1;
                        else
                            clk_sense_inv = 0;

                        /* If the two flops don't clock at the same time, then issue a  */
                        /* warning that the slack time loses half a clock period.       */

                        if ((verbose > 0) && (clk_invert != -1) && (clk_sense_inv != clk_invert)) {
                            fprintf(stdout, "   Clocks are inverted relative to one another,\n");
                            fprintf(stdout, "   implying a maximum propagation delay of 1/2 period.\n");
                        }
                    }
                    if (selecteddest != NULL && selectedsource != NULL) {
                        if (verbose > 0) {
                            if (minmax == MAXIMUM_TIME)
                                fprintf(stdout, "   %g setup time at destination\n", setupdelay);
                            else
                                fprintf(stdout, "   %g hold time at destination\n", holddelay);
                        }
                    }

                    if (verbose > 0) fprintf(stdout, "\n");
                }
            }
            else if (verbose > 0) {
                fprintf(stdout, "Path terminated on output \"%s\" with max delay %g ps\n",
                                testddata->backtrace->receiver->refnet->name, testddata->delay);

                backtrace = testddata->backtrace;
                fprintf(stdout, "   %g (%s) %s/%s -> [output pin]\n",
                        backtrace->delay,
                        backtrace->receiver->refnet->name,
                        backtrace->receiver->refnet->driver->refinst->name,
                        backtrace->receiver->refnet->driver->refpin->name);

                for (backtrace = backtrace->next; backtrace->next; backtrace = backtrace->next) {
                    fprintf(stdout, "   %g (%s) %s/%s -> %s/%s\n",
                                backtrace->delay,
                                backtrace->receiver->refnet->name,
                                backtrace->receiver->refnet->driver->refinst->name,
                                backtrace->receiver->refnet->driver->refpin->name,
                                backtrace->receiver->refinst->name,
                                backtrace->receiver->refpin->name);
                }
                fprintf(stdout, "   000.000 (%s) %s/%s -> %s/%s\n\n",
                        backtrace->receiver->refnet->name,
                        backtrace->receiver->refinst->name,
                        backtrace->receiver->refpin->name,
                        backtrace->receiver->refinst->name,
                        backtrace->receiver->refinst->out_connects->refpin->name);
            }

            // Clean up clock2list
            while (clock2list != NULL) {
                freebt = clock2list;
                clock2list = clock2list->next;
                free(freebt);
            }
        }

        // Link delaylist data to the beginning of masterlist, and null out
        // delaylist for the next set of paths.

        if (delaylist) {
            for (testddata = delaylist; testddata->next; testddata = testddata->next);
            testddata->next = *masterlist;
            *masterlist = delaylist;
            delaylist = NULL;
        }

        // Free up clocklist
        while (clocklist != NULL) {
            freebt = clocklist;
            clocklist = clocklist->next;
            free(freebt);
        }
    }
    return numpaths;
}

/*--------------------------------------------------------------*/
/* Parse a table variable type from a liberty format file       */
/*--------------------------------------------------------------*/

int get_table_type(char *token) {
    if (!strcasecmp(token, "input_net_transition"))
        return TRANSITION_TIME;
    else if (!strcasecmp(token, "total_output_net_capacitance"))
        return OUTPUT_CAP;
    else if (!strcasecmp(token, "related_pin_transition"))
        return RELATED_TIME;
    else if (!strcasecmp(token, "constrained_pin_transition"))
        return CONSTRAINED_TIME;
    else
        return UNKNOWN;
}

/*--------------------------------------------------------------*/
/* Read a liberty format file and collect information about     */
/* the timing properties of each standard cell.                 */
/*--------------------------------------------------------------*/

void
libertyRead(FILE *flib, lutable **tablelist, cell **celllist)
{
    char *token;
    char *libname = NULL;
    int section = INIT;

    double time_unit = 1.0;     // Time unit multiplier, to get ps
    double cap_unit = 1.0;      // Capacitive unit multiplier, to get fF

    pinptr testpin;
    lutable *tableptr;

    int i, j;
    double gval;
    char *iptr;
    short timing_type, sense_type;

    lutable *newtable, *reftable;
    cell *newcell, *lastcell;
    pin *newpin;

    lastcell = NULL;
    timing_type = UNKNOWN;

    /* Read tokens off of the line */
    token = advancetoken(flib, 0);

    while (token != NULL) {

        switch (section) {
            case INIT:
                if (!strcasecmp(token, "library")) {
                    token = advancetoken(flib, 0);
                    if (strcmp(token, "("))
                        fprintf(stderr, "Library not followed by name\n");
                    else
                        token = advancetoken(flib, ')');
                    fprintf(stderr, "Parsing library \"%s\"\n", token);
                    libname = strdup(token);
                    token = advancetoken(flib, 0);
                    if (strcmp(token, "{")) {
                        fprintf(stderr, "Did not find opening brace "
                                        "on library block\n");
                        exit(1);
                    }
                    section = LIBBLOCK;
                }
                else
                    fprintf(stderr, "Unknown input \"%s\", looking for "
                                        "\"library\"\n", token);
                break;

            case LIBBLOCK:
                // Here we check for the main blocks, again not rigorously. . .

                if (!strcasecmp(token, "}")) {
                    fprintf(stdout, "End of library at line %d\n", fileCurrentLine);
                    section = INIT;                     // End of library block
                }
                else if (!strcasecmp(token, "delay_model")) {
                    token = advancetoken(flib, 0);
                    if (strcmp(token, ":"))
                        fprintf(stderr, "Input missing colon\n");
                    token = advancetoken(flib, ';');
                    if (strcasecmp(token, "table_lookup")) {
                        fprintf(stderr, "Sorry, only know how to "
                                        "handle table lookup!\n");
                        exit(1);
                    }
                }
                else if (!strcasecmp(token, "lu_table_template") ||
                         !strcasecmp(token, "power_lut_template")) {
                    // Read in template information;
                    newtable = (lutable *)malloc(sizeof(lutable));
                    newtable->name = NULL;
                    newtable->invert = 0;
                    newtable->var1 = UNKNOWN;
                    newtable->var2 = UNKNOWN;
                    newtable->size1 = 0;
                    newtable->size2 = 0;
                    newtable->idx1.times = NULL;
                    newtable->idx2.caps = NULL;
                    newtable->values = NULL;
                    newtable->next = *tablelist;
                    *tablelist = newtable;

                    token = advancetoken(flib, 0);
                    if (strcmp(token, "("))
                        fprintf(stderr, "Input missing open parens\n");
                    else
                        token = advancetoken(flib, ')');
                    newtable->name = strdup(token);
                    while (*token != '}') {
                        token = advancetoken(flib, 0);
                        if (!strcasecmp(token, "variable_1")) {
                            token = advancetoken(flib, 0);
                            token = advancetoken(flib, ';');
                            newtable->var1 = get_table_type(token);
                            if (newtable->var1 == OUTPUT_CAP || newtable->var1 == CONSTRAINED_TIME)
                                newtable->invert = 1;
                        }
                        else if (!strcasecmp(token, "variable_2")) {
                            token = advancetoken(flib, 0);
                            token = advancetoken(flib, ';');
                            newtable->var2 = get_table_type(token);
                            if (newtable->var2 == TRANSITION_TIME || newtable->var2 == RELATED_TIME)
                                newtable->invert = 1;
                        }
                        else if (!strcasecmp(token, "index_1")) {
                            token = advancetoken(flib, 0);      // Open parens
                            token = advancetoken(flib, 0);      // Quote
                            if (!strcmp(token, "\""))
                                token = advancetoken(flib, '\"');

                            if (newtable->invert == 1) {
                                // Count entries
                                iptr = token;
                                newtable->size2 = 1;
                                while ((iptr = strchr(iptr, ',')) != NULL) {
                                    iptr++;
                                    newtable->size2++;
                                }
                                newtable->idx2.caps = (double *)malloc(newtable->size2 *
                                        sizeof(double));
                                newtable->size2 = 0;
                                iptr = token;
                                sscanf(iptr, "%lg", &newtable->idx2.caps[0]);
                                if (newtable->var2 == OUTPUT_CAP)
                                    newtable->idx2.caps[0] *= cap_unit;
                                else
                                    newtable->idx2.caps[0] *= time_unit;

                                while ((iptr = strchr(iptr, ',')) != NULL) {
                                    iptr++;
                                    newtable->size2++;
                                    sscanf(iptr, "%lg",
                                                &newtable->idx2.caps[newtable->size2]);
                                    if (newtable->var2 == OUTPUT_CAP)
                                        newtable->idx2.caps[newtable->size2] *= cap_unit;
                                    else
                                        newtable->idx2.cons[newtable->size2] *= time_unit;
                                }
                                newtable->size2++;
                            }
                            else {      // newtable->invert = 0
                                // Count entries
                                iptr = token;
                                newtable->size1 = 1;
                                while ((iptr = strchr(iptr, ',')) != NULL) {
                                    iptr++;
                                    newtable->size1++;
                                }
                                newtable->idx1.times = (double *)malloc(newtable->size1 *
                                        sizeof(double));
                                newtable->size1 = 0;
                                iptr = token;
                                sscanf(iptr, "%lg", &newtable->idx1.times[0]);
                                newtable->idx1.times[0] *= time_unit;
                                while ((iptr = strchr(iptr, ',')) != NULL) {
                                    iptr++;
                                    newtable->size1++;
                                    sscanf(iptr, "%lg",
                                                &newtable->idx1.times[newtable->size1]);
                                    newtable->idx1.times[newtable->size1] *= time_unit;
                                }
                                newtable->size1++;
                            }

                            token = advancetoken(flib, ';'); // EOL semicolon
                        }
                        else if (!strcasecmp(token, "index_2")) {
                            token = advancetoken(flib, 0);      // Open parens
                            token = advancetoken(flib, 0);      // Quote
                            if (!strcmp(token, "\""))
                                token = advancetoken(flib, '\"');

                            if (newtable->invert == 0) {
                                // Count entries
                                iptr = token;
                                newtable->size2 = 1;
                                while ((iptr = strchr(iptr, ',')) != NULL) {
                                    iptr++;
                                    newtable->size2++;
                                }
                                newtable->idx2.caps = (double *)malloc(newtable->size2 *
                                        sizeof(double));
                                newtable->size2 = 0;
                                iptr = token;
                                sscanf(iptr, "%lg", &newtable->idx2.caps[0]);
                                if (newtable->var2 == OUTPUT_CAP)
                                    newtable->idx2.caps[0] *= cap_unit;
                                else
                                    newtable->idx2.cons[0] *= time_unit;
                                while ((iptr = strchr(iptr, ',')) != NULL) {
                                    iptr++;
                                    newtable->size2++;
                                    sscanf(iptr, "%lg",
                                                &newtable->idx2.caps[newtable->size2]);
                                    if (newtable->var2 == OUTPUT_CAP)
                                        newtable->idx2.caps[newtable->size2] *= cap_unit;
                                    else
                                        newtable->idx2.cons[newtable->size2] *= time_unit;
                                }
                                newtable->size2++;
                            }
                            else {      // newtable->invert == 1
                                // Count entries
                                iptr = token;
                                newtable->size1 = 1;
                                while ((iptr = strchr(iptr, ',')) != NULL) {
                                    iptr++;
                                    newtable->size1++;
                                }
                                newtable->idx1.times = (double *)malloc(newtable->size1 *
                                        sizeof(double));
                                newtable->size1 = 0;
                                iptr = token;
                                sscanf(iptr, "%lg", &newtable->idx1.times[0]);
                                newtable->idx1.times[0] *= time_unit;
                                while ((iptr = strchr(iptr, ',')) != NULL) {
                                    iptr++;
                                    newtable->size1++;
                                    sscanf(iptr, "%lg",
                                                &newtable->idx1.times[newtable->size1]);
                                    newtable->idx1.times[newtable->size1] *= time_unit;
                                }
                                newtable->size1++;
                            }

                            token = advancetoken(flib, ';'); // EOL semicolon
                        }
                    }
                }
                else if (!strcasecmp(token, "cell")) {
                    newcell = (cell *)malloc(sizeof(cell));
                    newcell->next = NULL;
                    if (lastcell != NULL)
                        lastcell->next = newcell;
                    else
                        *celllist = newcell;
                    lastcell = newcell;
                    token = advancetoken(flib, 0);      // Open parens
                    if (!strcmp(token, "("))
                        token = advancetoken(flib, ')');        // Cellname
                    newcell->name = strdup(token);
                    token = advancetoken(flib, 0);      // Find start of block
                    if (strcmp(token, "{"))
                        fprintf(stderr, "Error: failed to find start of block\n");
                    newcell->type = GATE;               // Default type
                    newcell->function = NULL;
                    newcell->pins = NULL;
                    newcell->area = 1.0;
                    newcell->maxtrans = 0.0;
                    newcell->maxcap = 0.0;
                    section = CELLDEF;
                }
                else if (!strcasecmp(token, "time_unit")) {
                   char *metric;

                   token = advancetoken(flib, 0);
                   if (token == NULL) break;
                   if (!strcmp(token, ":")) {
                      token = advancetoken(flib, 0);
                      if (token == NULL) break;
                   }
                   if (!strcmp(token, "\"")) {
                      token = advancetoken(flib, '\"');
                      if (token == NULL) break;
                   }
                   time_unit = strtod(token, &metric);
                   if (*metric != '\0') {
                      if (!strcmp(metric, "ns"))
                         time_unit *= 1E3;
                      else if (!strcmp(metric, "us"))
                         time_unit *= 1E6;
                      else if (!strcmp(metric, "fs"))
                         time_unit *= 1E-3;
                      else if (strcmp(metric, "ps"))
                         fprintf(stderr, "Don't understand time units \"%s\"\n",
                                token);
                   }
                   else {
                      token = advancetoken(flib, 0);
                      if (token == NULL) break;
                      if (!strcmp(token, "ns"))
                         time_unit *= 1E3;
                      else if (!strcmp(token, "us"))
                         time_unit *= 1E6;
                      else if (!strcmp(token, "fs"))
                         time_unit *= 1E-3;
                      else if (strcmp(token, "ps"))
                         fprintf(stderr, "Don't understand time units \"%s\"\n",
                                token);
                   }
                   token = advancetoken(flib, ';');
                }
                else if (!strcasecmp(token, "capacitive_load_unit")) {
                   char *metric;

                   token = advancetoken(flib, 0);
                   if (token == NULL) break;
                   if (!strcmp(token, "(")) {
                      token = advancetoken(flib, ')');
                      if (token == NULL) break;
                   }
                   cap_unit = strtod(token, &metric);
                   if (*metric != '\0') {
                      while (isspace(*metric)) metric++;
                      if (*metric == ',') metric++;
                      while ((*metric != '\0') && isspace(*metric)) metric++;
                      if (!strcasecmp(metric, "af"))
                         cap_unit *= 1E-3;
                      else if (!strcasecmp(metric, "pf"))
                         cap_unit *= 1000;
                      else if (!strcasecmp(metric, "nf"))
                         cap_unit *= 1E6;
                      else if (!strcasecmp(metric, "uf"))
                         cap_unit *= 1E9;
                      else if (strcasecmp(metric, "ff"))
                         fprintf(stderr, "Don't understand capacitive units \"%s\"\n",
                                token);
                   }
                   else {
                      token = advancetoken(flib, 0);
                      if (token == NULL) break;
                      if (!strcasecmp(token, "af"))
                         cap_unit *= 1E-3;
                      else if (!strcasecmp(token, "pf"))
                         cap_unit *= 1000;
                      else if (!strcasecmp(token, "nf"))
                         cap_unit *= 1E6;
                      else if (!strcasecmp(token, "uf"))
                         cap_unit *= 1E9;
                      else if (strcasecmp(token, "ff"))
                         fprintf(stderr, "Don't understand capacitive units \"%s\"\n",
                                token);
                   }
                   token = advancetoken(flib, ';');
                }
                else {
                    // For unhandled tokens, read in tokens.  If it is
                    // a definition or function, read to end-of-line.  If
                    // it is a block definition, read to end-of-block.
                    while (1) {
                        token = advancetoken(flib, 0);
                        if (token == NULL) break;
                        if (!strcmp(token, ";")) break;
                        if (!strcmp(token, "\""))
                            token = advancetoken(flib, '\"');
                        if (!strcmp(token, "{")) {
                            token = advancetoken(flib, '}');
                            break;
                        }
                    }
                }
                break;

            case CELLDEF:

                if (!strcmp(token, "}")) {
                    section = LIBBLOCK;                 // End of cell def
                }
                else if (!strcasecmp(token, "pin")) {
                    token = advancetoken(flib, 0);      // Open parens
                    if (!strcmp(token, "("))
                        token = advancetoken(flib, ')');        // Close parens

                    newpin = parse_pin(newcell, token, SENSE_NONE);

                    token = advancetoken(flib, 0);      // Find start of block
                    if (strcmp(token, "{"))
                        fprintf(stderr, "Error: failed to find start of block\n");
                    section = PINDEF;
                }
                else if (!strcasecmp(token, "area")) {
                    token = advancetoken(flib, 0);      // Colon
                    token = advancetoken(flib, ';');    // To end-of-statement
                    sscanf(token, "%lg", &newcell->area);
                }
                else if (!strcasecmp(token, "ff")) {
                    newcell->type |= DFF;
                    token = advancetoken(flib, '{');
                    section = FLOPDEF;
                }
                else if (!strcasecmp(token, "latch")) {
                    newcell->type |= LATCH;
                    token = advancetoken(flib, '{');
                    section = LATCHDEF;
                }
                else {
                    // For unhandled tokens, read in tokens.  If it is
                    // a definition or function, read to end-of-line.  If
                    // it is a block definition, read to end-of-block.
                    while (1) {
                        token = advancetoken(flib, 0);
                        if (token == NULL) break;
                        if (!strcmp(token, ";")) break;
                        if (!strcmp(token, "\""))
                            token = advancetoken(flib, '\"');
                        if (!strcmp(token, "("))
                            token = advancetoken(flib, ')');
                        if (!strcmp(token, "{")) {
                            token = advancetoken(flib, '}');
                            break;
                        }
                    }
                }
                break;

            case FLOPDEF:

                if (!strcmp(token, "}")) {
                    section = CELLDEF;                  // End of flop def
                }
                else if (!strcasecmp(token, "next_state")) {
                    token = advancetoken(flib, 0);      // Colon
                    token = advancetoken(flib, ';');    // To end-of-statement
                    newpin = parse_pin(newcell, token, SENSE_NONE);
                    newpin->type |= DFFIN;
                }
                else if (!strcasecmp(token, "clocked_on")) {
                    token = advancetoken(flib, 0);      // Colon
                    token = advancetoken(flib, ';');    // To end-of-statement
                    if (strchr(token, '\'') != NULL)
                        newcell->type |= CLK_SENSE_MASK;
                    else if (strchr(token, '!') != NULL)
                        newcell->type |= CLK_SENSE_MASK;
                    newpin = parse_pin(newcell, token, SENSE_NONE);
                    newpin->type |= DFFCLK;
                }
                else if (!strcasecmp(token, "clear")) {
                    newcell->type |= RST_MASK;
                    token = advancetoken(flib, 0);      // Colon
                    token = advancetoken(flib, ';');    // To end-of-statement
                    if (strchr(token, '\'') != NULL)
                        newcell->type |= RST_SENSE_MASK;
                    else if (strchr(token, '!') != NULL)
                        newcell->type |= RST_SENSE_MASK;
                    newpin = parse_pin(newcell, token, SENSE_NONE);
                    newpin->type |= DFFRST;
                }
                else if (!strcasecmp(token, "preset")) {
                    newcell->type |= SET_MASK;
                    token = advancetoken(flib, 0);      // Colon
                    token = advancetoken(flib, ';');    // To end-of-statement
                    if (strchr(token, '\'') != NULL)
                        newcell->type |= SET_SENSE_MASK;
                    else if (strchr(token, '!') != NULL)
                        newcell->type |= SET_SENSE_MASK;
                    newpin = parse_pin(newcell, token, SENSE_NONE);
                    newpin->type |= DFFSET;
                }
                else
                    token = advancetoken(flib, ';');    // Read to end-of-statement

                break;

            case LATCHDEF:

                if (!strcmp(token, "}")) {
                    section = CELLDEF;                  // End of flop def
                }
                else if (!strcasecmp(token, "data_in")) {
                    token = advancetoken(flib, 0);      // Colon
                    token = advancetoken(flib, ';');    // To end-of-statement
                    newpin = parse_pin(newcell, token, SENSE_NONE);
                    newpin->type |= LATCHIN;
                }
                else if (!strcasecmp(token, "enable")) {
                    token = advancetoken(flib, 0);      // Colon
                    token = advancetoken(flib, ';');    // To end-of-statement
                    if (strchr(token, '\'') != NULL)
                        newcell->type |= EN_SENSE_MASK;
                    else if (strchr(token, '!') != NULL)
                        newcell->type |= EN_SENSE_MASK;
                    newpin = parse_pin(newcell, token, SENSE_NONE);
                    newpin->type |= LATCHEN;
                }
                else
                    token = advancetoken(flib, ';');    // Read to end-of-statement

                break;

            case PINDEF:

                if (!strcmp(token, "}")) {
                    section = CELLDEF;                  // End of pin def
                }
                else if (!strcasecmp(token, "capacitance")) {
                    token = advancetoken(flib, 0);      // Colon
                    token = advancetoken(flib, ';');    // To end-of-statement
                    sscanf(token, "%lg", &newpin->capr);
                    newpin->capr *= cap_unit;
                }
                else if (!strcasecmp(token, "rise_capacitance")) {
                    token = advancetoken(flib, 0);      // Colon
                    token = advancetoken(flib, ';');    // To end-of-statement
                    sscanf(token, "%lg", &newpin->capr);
                    newpin->capr *= cap_unit;
                }
                else if (!strcasecmp(token, "fall_capacitance")) {
                    token = advancetoken(flib, 0);      // Colon
                    token = advancetoken(flib, ';');    // To end-of-statement
                    sscanf(token, "%lg", &newpin->capf);
                    newpin->capf *= cap_unit;
                }
                else if (!strcasecmp(token, "function")) {
                    token = advancetoken(flib, 0);      // Colon
                    token = advancetoken(flib, 0);      // Open quote
                    if (!strcmp(token, "\""))
                        token = advancetoken(flib, '\"');       // Find function string
                    if (newpin->type & OUTPUT) {
                        newcell->function = strdup(token);
                    }
                    token = advancetoken(flib, 0);
                    if (strcmp(token, ";")) {
                        if (!strcmp(token, "}"))
                            section = CELLDEF;          // End of pin def
                        else
                            fprintf(stderr, "Expected end-of-statement.\n");
                    }
                }
                else if (!strcasecmp(token, "direction")) {
                    token = advancetoken(flib, 0);      // Colon
                    token = advancetoken(flib, ';');
                    if (!strcasecmp(token, "input")) {
                        newpin->type |= INPUT;
                    }
                    else if (!strcasecmp(token, "output")) {
                        newpin->type |= OUTPUT;
                        if (newcell->type & DFF) newpin->type |= DFFOUT;
                        if (newcell->type & LATCH) newpin->type |= LATCHOUT;
                    }
                }
                else if (!strcasecmp(token, "max_transition")) {
                    token = advancetoken(flib, 0);      // Colon
                    token = advancetoken(flib, ';');    // To end-of-statement
                    sscanf(token, "%lg", &newcell->maxtrans);
                    newcell->maxtrans *= time_unit;
                }
                else if (!strcasecmp(token, "max_capacitance")) {
                    token = advancetoken(flib, 0);      // Colon
                    token = advancetoken(flib, ';');    // To end-of-statement
                    sscanf(token, "%lg", &newcell->maxcap);
                    newcell->maxcap *= cap_unit;
                }
                else if (!strcasecmp(token, "timing")) {
                    token = advancetoken(flib, 0);      // Arguments, if any
                    if (strcmp(token, "("))
                        fprintf(stderr, "Error: failed to find start of block\n");
                    else
                       token = advancetoken(flib, ')'); // Arguments, if any
                    token = advancetoken(flib, 0);      // Find start of block
                    if (strcmp(token, "{"))
                        fprintf(stderr, "Error: failed to find start of block\n");
                    testpin = NULL;
                    sense_type = SENSE_NONE;
                    section = TIMING;
                }
                else {
                    // For unhandled tokens, read in tokens.  If it is
                    // a definition or function, read to end-of-line.  If
                    // it is a block definition, read to end-of-block.
                    while (1) {
                        token = advancetoken(flib, 0);
                        if (token == NULL) break;
                        if (!strcmp(token, ";")) break;
                        if (!strcmp(token, "\""))
                            token = advancetoken(flib, '\"');
                        if (!strcmp(token, "{")) {
                            token = advancetoken(flib, '}');
                            break;
                        }
                    }
                }
                break;

            case TIMING:

                if (!strcmp(token, "}")) {
                    section = PINDEF;                   // End of timing def
                }
                else if (!strcasecmp(token, "related_pin")) {
                    token = advancetoken(flib, 0);      // Colon
                    token = advancetoken(flib, ';');    // Read to end of statement
                    // Create the related pin if needed
                    testpin = parse_pin(newcell, token, sense_type);
                }
                else if (!strcasecmp(token, "timing_sense")) {
                    token = advancetoken(flib, 0);      // Colon
                    token = advancetoken(flib, ';');    // Read to end of statement
                    if (!strcasecmp(token, "positive_unate")) {
                        if (testpin)
                           testpin->sense = SENSE_POSITIVE;
                        else
                           sense_type = SENSE_POSITIVE;
                    }
                    else if (!strcasecmp(token, "negative_unate")) {
                        if (testpin)
                           testpin->sense = SENSE_NEGATIVE;
                        else
                           sense_type = SENSE_NEGATIVE;
                    }
                    else if (!strcasecmp(token, "non_unate")) {
                        if (testpin)
                           testpin->sense = SENSE_NONE;
                        else
                           sense_type = SENSE_NONE;
                    }
                }
                else if (!strcasecmp(token, "timing_type")) {
                    token = advancetoken(flib, 0);      // Colon
                    token = advancetoken(flib, ';');    // Read to end of statement

                    // Note:  Timing type is apparently redundant information;
                    // e.g., "falling_edge" can be determined by "clocked_on : !CLK"
                    // in the ff {} block.  How reliable is this?

                    if (!strcasecmp(token, "rising_edge"))
                        timing_type = TIMING_PROP_TRANS;
                    else if (!strcasecmp(token, "falling_edge"))
                        timing_type = TIMING_PROP_TRANS;
                    else if (!strcasecmp(token, "hold_rising"))
                        timing_type = TIMING_HOLD;
                    else if (!strcasecmp(token, "hold_falling"))
                        timing_type = TIMING_HOLD;
                    else if (!strcasecmp(token, "setup_rising"))
                        timing_type = TIMING_SETUP;
                    else if (!strcasecmp(token, "setup_falling"))
                        timing_type = TIMING_SETUP;
                    else if (!strcasecmp(token, "clear"))
                        timing_type = TIMING_SET_RESET;
                    else if (!strcasecmp(token, "preset"))
                        timing_type = TIMING_SET_RESET;
                    else if (!strcasecmp(token, "recovery_rising"))
                        timing_type = TIMING_RECOVERY;
                    else if (!strcasecmp(token, "recovery_falling"))
                        timing_type = TIMING_RECOVERY;
                    else if (!strcasecmp(token, "removal_rising"))
                        timing_type = TIMING_REMOVAL;
                    else if (!strcasecmp(token, "removal_falling"))
                        timing_type = TIMING_REMOVAL;
                    else if (!strcasecmp(token, "three_state_enable"))
                        timing_type = TIMING_TRISTATE;
                    else if (!strcasecmp(token, "three_state_disable"))
                        timing_type = TIMING_TRISTATE;
                }
                else if ((!strcasecmp(token, "cell_rise")) ||
                        (!strcasecmp(token, "cell_fall")) ||
                        (!strcasecmp(token, "rise_transition")) ||
                        (!strcasecmp(token, "fall_transition")) ||
                        (!strcasecmp(token, "rise_constraint")) ||
                        (!strcasecmp(token, "fall_constraint"))) {

                    tableptr = (lutable *)malloc(sizeof(lutable));
                    tableptr->name = NULL;      // Not used
                    tableptr->invert = 0;
                    tableptr->var1 = UNKNOWN;
                    tableptr->var2 = UNKNOWN;
                    tableptr->size1 = 0;
                    tableptr->size2 = 0;
                    tableptr->idx1.times = NULL;
                    tableptr->idx2.caps = NULL;
                    tableptr->values = NULL;
                    tableptr->next = NULL;      // Not used

                    // Note that propagation delays (cell rise, cell fall) and
                    // transition times (rise transition, fall transition) have
                    // their lookup tables stored in the "related pin" pin record.
                    // Setup and hold times (rise constraint, fall constraint)
                    // have their lookup tables stored in the original pin record.
                    // These should not overlap.

                    // Recovery and removal tables are not yet handled. . .

                    if (!strcasecmp(token, "cell_rise"))
                        testpin->propdelr = tableptr;
                    else if (!strcasecmp(token, "cell_fall"))
                        testpin->propdelf = tableptr;
                    else if (!strcasecmp(token, "rise_transition"))
                        testpin->transr = tableptr;
                    else if (!strcasecmp(token, "fall_transition"))
                        testpin->transf = tableptr;
                    else if (!strcasecmp(token, "rise_constraint")) {
                        if (timing_type == TIMING_SETUP)
                            newpin->propdelr = tableptr;
                        else if (timing_type == TIMING_HOLD)
                            newpin->transr = tableptr;
                    }
                    else if (!strcasecmp(token, "fall_constraint")) {
                        if (timing_type == TIMING_SETUP)
                            newpin->propdelf = tableptr;
                        else if (timing_type == TIMING_HOLD)
                            newpin->transf = tableptr;
                    }

                    token = advancetoken(flib, 0);      // Open parens
                    if (!strcmp(token, "("))
                        token = advancetoken(flib, ')');

                    for (reftable = *tablelist; reftable; reftable = reftable->next)
                        if (!strcmp(reftable->name, token))
                            break;
                    if (reftable == NULL)
                        fprintf(stderr, "Failed to find a valid table \"%s\"\n",
                                token);
                    else {
                        // Fill in default values from template reftable
                        tableptr->invert = reftable->invert;
                        if (reftable->size1 > 0) {
                            tableptr->var1 = reftable->var1;
                            tableptr->size1 = reftable->size1;
                            tableptr->idx1.times = (double *)malloc(tableptr->size1 * sizeof(double));
                            memcpy(tableptr->idx1.times, reftable->idx1.times,
                                                tableptr->size1 * sizeof(double));
                        }
                        if (reftable->size2 > 0) {
                            tableptr->var2 = reftable->var2;
                            tableptr->size2 = reftable->size2;
                            tableptr->idx2.caps = (double *)malloc(tableptr->size2 * sizeof(double));
                            memcpy(tableptr->idx2.caps, reftable->idx2.caps,
                                                tableptr->size2 * sizeof(double));
                        }
                    }

                    token = advancetoken(flib, 0);
                    if (strcmp(token, "{"))
                        fprintf(stderr, "Failed to find start of timing block\n");

                    while (*token != '}') {
                        token = advancetoken(flib, 0);
                        if (!strcasecmp(token, "index_1")) {

                            // Local index values override those in the template

                            token = advancetoken(flib, 0);      // Open parens
                            token = advancetoken(flib, 0);      // Quote
                            if (!strcmp(token, "\""))
                                token = advancetoken(flib, '\"');

                            //-------------------------

                            if (reftable && (reftable->invert == 1)) {
                                // Entries had better match the ref table
                                iptr = token;
                                i = 0;
                                sscanf(iptr, "%lg", &tableptr->idx2.caps[0]);
                                if (tableptr->var2 == OUTPUT_CAP)
                                    tableptr->idx2.caps[0] *= cap_unit;
                                else
                                    tableptr->idx2.cons[0] *= time_unit;
                                while ((iptr = strchr(iptr, ',')) != NULL) {
                                    iptr++;
                                    i++;
                                    sscanf(iptr, "%lg", &tableptr->idx2.caps[i]);
                                    if (tableptr->var2 == OUTPUT_CAP)
                                        tableptr->idx2.caps[i] *= cap_unit;
                                    else
                                        tableptr->idx2.cons[i] *= time_unit;
                                }
                            }
                            else if (reftable && (reftable->invert == 0)) {
                                iptr = token;
                                i = 0;
                                sscanf(iptr, "%lg", &tableptr->idx1.times[0]);
                                tableptr->idx1.times[0] *= time_unit;
                                while ((iptr = strchr(iptr, ',')) != NULL) {
                                    iptr++;
                                    i++;
                                    sscanf(iptr, "%lg", &tableptr->idx1.times[i]);
                                    tableptr->idx1.times[i] *= time_unit;
                                }
                            }

                            token = advancetoken(flib, ')');    // Close paren
                            token = advancetoken(flib, ';');    // EOL semicolon
                        }
                        else if (!strcasecmp(token, "index_2")) {

                            // Local index values override those in the template

                            token = advancetoken(flib, 0);      // Open parens
                            token = advancetoken(flib, 0);      // Quote
                            if (!strcmp(token, "\""))
                                token = advancetoken(flib, '\"');

                            //-------------------------

                            if (reftable && (reftable->invert == 1)) {
                                // Entries had better match the ref table
                                iptr = token;
                                i = 0;
                                sscanf(iptr, "%lg", &tableptr->idx1.times[0]);
                                tableptr->idx1.times[0] *= time_unit;
                                while ((iptr = strchr(iptr, ',')) != NULL) {
                                    iptr++;
                                    i++;
                                    sscanf(iptr, "%lg", &tableptr->idx1.times[i]);
                                    tableptr->idx1.times[i] *= time_unit;
                                }
                            }
                            else if (reftable && (reftable->invert == 0)) {
                                iptr = token;
                                i = 0;
                                sscanf(iptr, "%lg", &tableptr->idx2.caps[0]);
                                tableptr->idx2.caps[0] *= cap_unit;
                                while ((iptr = strchr(iptr, ',')) != NULL) {
                                    iptr++;
                                    i++;
                                    sscanf(iptr, "%lg", &tableptr->idx2.caps[i]);
                                    tableptr->idx2.caps[i] *= cap_unit;
                                }
                            }

                            token = advancetoken(flib, ')');    // Close paren
                            token = advancetoken(flib, ';');    // EOL semicolon
                        }
                        else if (!strcasecmp(token, "values")) {
                            token = advancetoken(flib, 0);
                            if (strcmp(token, "("))
                                fprintf(stderr, "Failed to find start of"
                                                " value table\n");
                            token = advancetoken(flib, ')');

                            // Parse the string of values and enter it into the
                            // table "values", which is size size2 x size1

                            if (reftable && reftable->size1 > 0) {
                                int locsize2;
                                locsize2 = (reftable->size2 > 0) ? reftable->size2 : 1;
                                if (reftable->invert) {
                                    tableptr->values = (double *)malloc(locsize2 *
                                                reftable->size1 * sizeof(double));
                                    iptr = token;
                                    for (i = 0; i < reftable->size1; i++) {
                                        for (j = 0; j < locsize2; j++) {
                                            while (*iptr == ' ' || *iptr == '\"' ||
                                                        *iptr == ',')
                                                iptr++;
                                            sscanf(iptr, "%lg", &gval);
                                            *(tableptr->values + j * reftable->size1
                                                        + i) = gval * time_unit;
                                            while (*iptr != ' ' && *iptr != '\"' &&
                                                        *iptr != ',')
                                                iptr++;
                                        }
                                    }
                                }
                                else {
                                    tableptr->values = (double *)malloc(locsize2 *
                                                reftable->size1 * sizeof(double));
                                    iptr = token;
                                    for (j = 0; j < locsize2; j++) {
                                        for (i = 0; i < reftable->size1; i++) {
                                            while (*iptr == ' ' || *iptr == '\"' ||
                                                        *iptr == ',')
                                                iptr++;
                                            sscanf(iptr, "%lg", &gval);
                                            *(tableptr->values + j * reftable->size1
                                                        + i) = gval * time_unit;
                                            while (*iptr != ' ' && *iptr != '\"' &&
                                                        *iptr != ',')
                                                iptr++;
                                        }
                                    }
                                }
                            }

                            token = advancetoken(flib, 0);
                            if (strcmp(token, ";"))
                                fprintf(stderr, "Failed to find end of value table\n");
                            token = advancetoken(flib, 0);

                        }
                        else if (strcmp(token, "{"))
                            fprintf(stderr, "Failed to find end of timing block\n");
                    }
                }
                else {
                    // For unhandled tokens, read in tokens.  If it is
                    // a definition or function, read to end-of-line.  If
                    // it is a block definition, read to end-of-block.
                    while (1) {
                        token = advancetoken(flib, 0);
                        if (token == NULL) break;
                        if (!strcmp(token, ";")) break;
                        if (!strcmp(token, "\""))
                            token = advancetoken(flib, '\"');
                        if (!strcmp(token, "{")) {
                            token = advancetoken(flib, '}');
                            break;
                        }
                    }
                }
                break;
        }
        token = advancetoken(flib, 0);
    }
}

/*--------------------------------------------------------------*/
/* Read a verilog netlist and collect information about the     */
/* cells instantiated and the network structure                 */
/*--------------------------------------------------------------*/

void
verilogRead(FILE *fsrc, cell *cells, net **netlist, instance **instlist,
                connect **inputlist, connect **outputlist, struct hashlist **Nethash)
{
    char *token;
    char *modname = NULL;
    int section = MODULE;

    instptr newinst;
    netptr newnet, testnet;
    cellptr testcell;
    connptr newconn, testconn;
    pinptr testpin;

    int vstart, vend, vtarget, isinput;

    /* Read tokens off of the line */
    token = advancetoken(fsrc, 0);

    while (token != NULL) {

        switch (section) {
            case MODULE:
                if (!strcasecmp(token, "module")) {
                    token = advancetoken(fsrc, 0);
                    fprintf(stderr, "Parsing module \"%s\"\n", token);

                    token = advancetoken(fsrc, 0);
                    if (strcmp(token, "("))
                        fprintf(stderr, "Module not followed by pin list\n");
                    else
                        token = advancetoken(fsrc, ')');
                    token = advancetoken(fsrc, ';');    // Get end-of-line

                    // Ignore the pin list, go straight to the input/output declarations
                    section = IOLIST;
                }
                break;

            case IOLIST:
                if (!strcasecmp(token, "input") || !strcasecmp(token, "output")) {

                    testconn = NULL;
                    vstart = vend = 0;

                    if (!strcasecmp(token, "input")) {
                        isinput = 1;
                    }
                    else {      // output
                        isinput = 0;
                    }

                    token = advancetoken(fsrc, 0);
                    if (*token == '[') {
                        sscanf(token + 1, "%d", &vstart);
                        token = advancetoken(fsrc, ':');
                        token = advancetoken(fsrc, ']');        // Read to end of vector
                        sscanf(token, "%d", &vend);
                        token = advancetoken(fsrc, 0);          // Read signal name
                    }

                    // Create a net entry for the input or output, add to the list of nets

                    if (vstart == 0 && vend == 0) {
                        newnet = create_net(netlist);
                        newnet->name = strdup(token);
                        HashPtrInstall(newnet->name, newnet, Nethash);

                        testconn = (connptr)malloc(sizeof(connect));
                        testconn->refnet = newnet;
                        testconn->refpin = NULL;        // No associated pin
                        testconn->refinst = NULL;       // No associated instance
                        testconn->tag = NULL;
                        testconn->metric = -1.0;
                        testconn->prvector = NULL;
                        testconn->pfvector = NULL;
                        testconn->trvector = NULL;
                        testconn->tfvector = NULL;

                        if (isinput) {                  // driver (input)
                            testconn->next = *inputlist;
                            *inputlist = testconn;
                        }
                        else {                          // receiver (output)
                            testconn->next = *outputlist;
                            *outputlist = testconn;
                        }
                    }
                    else {
                        vtarget = vend + ((vstart < vend) ? 1 : -1);
                        while (vstart != vtarget) {
                            newnet = create_net(netlist);
                            newnet->name = (char *)malloc(strlen(token) + 6);
                            sprintf(newnet->name, "%s[%d]", token, vstart);
                            HashPtrInstall(newnet->name, newnet, Nethash);

                            vstart += (vtarget > vend) ? 1 : -1;

                            testconn = (connptr)malloc(sizeof(connect));
                            testconn->refnet = newnet;
                            testconn->refpin = NULL;    // No associated pin
                            testconn->refinst = NULL;   // No associated instance
                            testconn->tag = NULL;
                            testconn->metric = -1.0;
                            testconn->prvector = NULL;
                            testconn->pfvector = NULL;
                            testconn->trvector = NULL;
                            testconn->tfvector = NULL;

                            if (isinput) {              // driver (input)
                                testconn->next = *inputlist;
                                *inputlist = testconn;
                            }
                            else {                      // receiver (output)
                                testconn->next = *outputlist;
                                *outputlist = testconn;
                            }
                        }
                    }
                    token = advancetoken(fsrc, ';');    // Get rest of input/output entry
                    break;
                }

                /* Drop through on anything that isn't an input, output, or blank line */

            case GATELIST:

                if (!strcasecmp(token, "endmodule")) {
                    section = MODULE;
                    break;
                }

                /* Confirm that the token is a known cell, and continue parsing line if so */
                /* Otherwise, parse to semicolon line end and continue */

                for (testcell = cells; testcell; testcell = testcell->next)
                    if (!strcasecmp(testcell->name, token))
                        break;

                if (testcell != NULL) {
                    section = INSTANCE;
                    newinst = (instptr)malloc(sizeof(instance));
                    newinst->next = *instlist;
                    *instlist = newinst;
                    newinst->refcell = testcell;
                    newinst->in_connects = NULL;
                    newinst->out_connects = NULL;
                }
                else {
                    /* Ignore all wire and assign statements    */
                    /* Qflow does not generate these, but other */
                    /* synthesis tools may.                     */

                    if (!strcasecmp(token, "assign") && (verbose > 0)) {
                        fprintf(stdout, "Wire assignments are not handled!\n");
                    }
                    else if (strcasecmp(token, "wire") && (verbose > 0)) {
                        fprintf(stdout, "Unknown cell \"%s\" instanced.\n",
                                token);
                    }
                    token = advancetoken(fsrc, ';');    // Get rest of entry, and ignore
                }
                break;

            case INSTANCE:
                newinst->name = strdup(token);
                token = advancetoken(fsrc, '(');        // Find beginning of pin list
                section = INSTPIN;
                break;

            case INSTPIN:
                if (*token == '.') {
                    newconn = (connptr)malloc(sizeof(connect));
                    // Pin name is in (token + 1)
                    for (testpin = testcell->pins; testpin; testpin = testpin->next) {
                        if (!strcmp(testpin->name, token + 1))
                            break;
                    }
                    // Sanity check
                    if (testpin == NULL) {
                        fprintf(stderr, "No such pin \"%s\" in cell \"%s\"!\n",
                                token + 1, testcell->name);
                    }
                    else {
                        if (testpin->type & OUTPUT) {
                            newconn->next = newinst->out_connects;
                            newinst->out_connects = newconn;
                        }
                        else {
                            newconn->next = newinst->in_connects;
                            newinst->in_connects = newconn;
                        }
                    }
                    newconn->refinst = newinst;
                    newconn->refpin = testpin;
                    newconn->refnet = NULL;
                    newconn->tag = NULL;
                    newconn->metric = -1.0;
                    newconn->prvector = NULL;
                    newconn->pfvector = NULL;
                    newconn->trvector = NULL;
                    newconn->tfvector = NULL;
                    token = advancetoken(fsrc, '(');    // Read to beginning of pin name
                    section = PINCONN;
                }
                else if (*token == ';') {
                    // End of instance record
                    section = GATELIST;
                }
                else if (*token != ',' && *token != ')') {
                    fprintf(stderr, "Unexpected entry in instance pin connection list!\n");
                    token = advancetoken(fsrc, ';');    // Read to end-of-line
                    section = GATELIST;
                }
                break;

            case PINCONN:
                // Token is net name
                testnet = (netptr)HashLookup(token, Nethash);
                if (testnet == NULL) {
                    // This is a new net, and we need to record it
                    newnet = create_net(netlist);
                    newnet->name = strdup(token);
                    HashPtrInstall(newnet->name, newnet, Nethash);
                    newconn->refnet = newnet;
                }
                else
                    newconn->refnet = testnet;
                section = INSTPIN;
                break;
        }
        if (section == PINCONN)
            token = advancetoken(fsrc, ')');    // Name token parsing
        else
            token = advancetoken(fsrc, 0);
    }

}

/*--------------------------------------------------------------*/
/* For each net, go through the list of receivers and add the   */
/* contributions of each to the total load.  This is either     */
/* the input pin capacitance, if the receiver is a pin, or the  */
/* designated output load (given on the command line), if the   */
/* receiver is an output pin.                                   */
/*--------------------------------------------------------------*/

void
computeLoads(netptr netlist, instptr instlist, double out_load)
{
    instptr testinst;
    pinptr testpin;
    netptr testnet, driver, loadnet;
    connptr testconn;
    int i;

    for (testnet = netlist; testnet; testnet = testnet->next) {
        for (i = 0; i < testnet->fanout; i++) {
            testconn = testnet->receivers[i];
            testpin = testconn->refpin;
            if (testpin == NULL) {
                testnet->loadr += out_load;
                testnet->loadf += out_load;
            }
            else {
                testnet->loadr += testpin->capr;
                testnet->loadf += testpin->capf;
            }
        }
    }

    // For each instance input pin, collapse the pin's lookup table
    // to a vector by interpolating/extrapolating the table at the
    // calculated output load.  Save this vector in the connection
    // record for the pin.

    for (testinst = instlist; testinst; testinst = testinst->next) {
        loadnet = testinst->out_connects->refnet;
        for (testconn = testinst->in_connects; testconn; testconn = testconn->next) {
            testpin = testconn->refpin;

            if (testpin->propdelr)
                testconn->prvector = table_collapse(testpin->propdelr, loadnet->loadr);
            if (testpin->propdelf)
                testconn->pfvector = table_collapse(testpin->propdelf, loadnet->loadf);
            if (testpin->transr)
                testconn->trvector = table_collapse(testpin->transr, loadnet->loadr);
            if (testpin->transf)
                testconn->tfvector = table_collapse(testpin->transf, loadnet->loadf);
        }
    }
}

/*--------------------------------------------------------------*/
/* Assign types to each net.  This identifies which nets are    */
/* clock inputs, which are latch enable inputs, and which are   */
/* asynchronous set/reset inputs,.                              */
/*                                                              */
/* Whenever a clock input to a flop is found, add the           */
/* connection record to clockedlist                             */
/*                                                              */
/* For diagnostics, return the number of entries in clockedlist */
/*--------------------------------------------------------------*/

int assign_net_types(netptr netlist, connlistptr *clockedlist)
{
    int i, numterms;
    netptr testnet;
    connptr testrcvr;
    pinptr testpin;
    connlistptr newclocked;

    numterms = 0;

    for (testnet = netlist; testnet; testnet = testnet->next) {
        for (i = 0; i < testnet->fanout; i++) {
            testrcvr = testnet->receivers[i];
            testpin = testrcvr->refpin;
            if (testpin == NULL)
                testnet->type |= OUTTERM;
            else {
                switch (testpin->type & (DFFMASK | LATCHMASK)) {
                    case DFFCLK:
                        testnet->type |= CLOCK;
                        newclocked = (connlistptr)malloc(sizeof(connlist));
                        newclocked->connection = testrcvr;
                        newclocked->next = *clockedlist;
                        *clockedlist = newclocked;
                        numterms++;
                        break;
                    case DFFIN:
                        testnet->type |= TERMINAL;
                        break;
                    case DFFSET:
                    case DFFRST:
                        testnet->type |= ASYNC;
                        break;
                    case LATCHIN:
                        testnet->type |= LATCHTERM;
                        break;
                    case LATCHEN:
                        testnet->type |= ENABLE;
                        break;
                }
            }
        }
    }
    return numterms;
}

/*--------------------------------------------------------------*/
/* Create the links representing the netlist connections.       */
/*                                                              */
/* The verilogRead() routine added all nets and instances, and  */
/* for each instance, generated a list of net connections to    */
/* each pin.  To make the netlist easily traversible, this      */
/* routine does the following:                                  */
/*                                                              */
/* For each instance, work through the list of pin              */
/*      connections.  If the pin is an output, then add the     */
/*      connection as the net's driver entry.  If the pin is    */
/*      an input, then add the connection to the list of the    */
/*      net's receivers, and increment the net's fanout.        */
/*                                                              */
/* For each module input, add the input connection as the       */
/*      net's driver (flag an error if the net already has a    */
/*      driver).                                                */
/*                                                              */
/* For each module output, add the output connection as one of  */
/*      the net's receivers (it may be the only one).           */
/*                                                              */
/*--------------------------------------------------------------*/

void
createLinks(netptr netlist, instptr instlist, connptr inputlist, connptr outputlist)
{
    netptr testnet;
    instptr testinst;
    connptr testconn;

    for (testinst = instlist; testinst; testinst = testinst->next) {
        for (testconn = testinst->in_connects; testconn; testconn = testconn->next) {
            testnet = testconn->refnet;
            testnet->fanout++;
            if (testnet->receivers == NULL)
                testnet->receivers = (connptr *)malloc(sizeof(connptr));

            else
                testnet->receivers = (connptr *)realloc(testnet->receivers,
                                testnet->fanout * sizeof(connptr));

            testnet->receivers[testnet->fanout - 1] = testconn;
        }

        for (testconn = testinst->out_connects; testconn; testconn = testconn->next) {
            testnet = testconn->refnet;
            testnet->driver = testconn;
        }
    }

    for (testconn = inputlist; testconn; testconn = testconn->next) {
        testnet = testconn->refnet;
        if (testnet->driver != NULL)
            fprintf(stderr, "Error:  Input pin \"%s\" has an internal driver!\n",
                        testnet->name);
        // else
        //    testnet->driver = testconn;       // Don't do this, makes connectivity circular
    }

    for (testconn = outputlist; testconn; testconn = testconn->next) {
        testnet = testconn->refnet;
        testnet->fanout++;
        if (testnet->receivers == NULL)
            testnet->receivers = (connptr *)malloc(sizeof(connptr));

        else
            testnet->receivers = (connptr *)realloc(testnet->receivers,
                        testnet->fanout * sizeof(connptr));

        testnet->receivers[testnet->fanout - 1] = testconn;
    }
}

/*--------------------------------------------------------------*/
/* Delay comparison used by qsort() to sort paths in order from */
/* longest to shortest propagation delay.                       */
/*--------------------------------------------------------------*/

int
compdelay(ddataptr *a, ddataptr *b)
{
    ddataptr p = *a;
    ddataptr q = *b;

    if (p->delay < q->delay)
        return (1);
    if (p->delay > q->delay)
        return (-1);
    return (0);
}

void
delayRead(FILE *fdly, struct hashlist **Nethash)
{
    fprintf(stdout, "delayRead\n");
    char c[128];
    char d[128];
    char *token;

    netptr newnet, testnet;
    connptr testconn;
    pinptr testpin;
    int i;
    int numRxers;

    token = advancetoken(fdly, 0);

    while (token != NULL) {

        numRxers = 0;
        testnet = (netptr)HashLookup(token, Nethash);

        char *saveptr;
        char *saveptr2;

        // Read driver of interconnect and total interconnect capacitance
        fgets(c, 128, fdly);
        //fprintf(stdout, "\tDriver Inst: %s\n", strtok_r(c, "/", &saveptr));
        //fprintf(stdout, "\tDriver Pin: %s\n", strtok_r(NULL, " ", &saveptr));
        //fprintf(stdout, "\tTotC: %f\n", strtof(saveptr, NULL));

        testnet->loadr = (strtod(saveptr, NULL)) / 1e3;
        testnet->loadf = testnet->loadr;

        fgets(c, 128, fdly);

        while (c[0] != '\n') {
            //if (debug > 0) fprintf(stdout, "\t%s\n", c);

            //separate receiver name and delay value
            strtok_r(c, " ", &saveptr);
            //fprintf(stdout, "\tDelay: %s\n", saveptr);
            //fprintf(stdout, "\tRxer Name: %s\n", c);
            strtok_r(c, "/", &saveptr2);
            //fprintf(stdout, "\tRxer Name: %s\n", c);
            //fprintf(stdout, "\tRxer Name: %s\n", saveptr2);
            //fprintf(stdout, "\tDelay: %f\n", strtof(saveptr, NULL));

            for (i = 0; i < testnet->fanout; i++) {

                testconn = testnet->receivers[i];

                if (testnet->type == OUTTERM) {
                    fprintf(stdout, "\tNet connects to output and has no receiving instance pin\n");
                    testconn->icDelay = strtof(saveptr, NULL);
                } else {
                    fprintf(stdout, "\trefinstname: %s\n", testconn->refinst->name);

                    if (!strcmp(testconn->refinst->name, c)) {

                        testconn->icDelay = strtof(saveptr, NULL);
                        break;
                    }
                }
                //fprintf(stdout, "\tName: %s\n", c);
            }

            fgets(c, 128, fdly);
            numRxers += 1;
        }

        if (numRxers != testnet->fanout) {
            fprintf(stderr, "ERROR: Net %s did not have a number of receivers in delay file equal to the fanout of %d", testnet->name, testnet->fanout);
            exit(-1);
        }

        token = advancetoken(fdly, 0);
    }
}

/*--------------------------------------------------------------*/
/* Print a path component                                       */
/* (code contributed by Karl-Filip Faxen)			*/
/*--------------------------------------------------------------*/

void
print_path_component(int netFWidth, int instFWidth, int pinFWidth, int recvFWidth, 
                     btptr backtrace)
{
    // Return immediately if we have niether a driver or a receiver instance
    if( backtrace->receiver->refnet->driver == NULL && backtrace->receiver->refinst == NULL ) return;

    fprintf(stdout, " %8.1f ps", backtrace->delay);
    if( backtrace->receiver->refnet != NULL ) {
        netptr net = backtrace->receiver->refnet;
        fprintf(stdout, "  %*s: ", netFWidth, net->name);
        if( net->driver != NULL ) {   // If the driver exists, it has a refinst
            fprintf(stdout, "%*s/%*s",
                    instFWidth, net->driver->refinst->name,
                    -pinFWidth, net->driver->refpin->name);
        }
        else {
            fprintf(stdout, "%*s", instFWidth+pinFWidth+1, "");
        }
    }
    fprintf(stdout, " -> ");
    if( backtrace->receiver->refinst != NULL ) {
        fprintf(stdout, "%*s/%s",
                recvFWidth, backtrace->receiver->refinst->name,
                              backtrace->receiver->refpin->name);
    }
    fprintf(stdout, "\n");
}

/*--------------------------------------------------------------*/
/* Print a path                                                 */
/* (code contributed by Karl-Filip Faxen)			*/
/*--------------------------------------------------------------*/

void
print_path(btptr backtrace)
{
    int netFWidth = 0;
    int instFWidth = 0;
    int pinFWidth = 0;
    int recvFWidth = 0;
    btptr curr = backtrace, prev = NULL;

    // The back trace is last entry first, so we do one pointer reversal to
    // get it in first entry first order, then we reverse it back again.

    while( curr != NULL ) {
        // Find max length of net name, inst name and pin name
        netptr net = curr->receiver->refnet;
        if( net != NULL ) {
            int namelen = strlen(net->name);
            if(namelen > netFWidth) netFWidth = namelen;
            if( net->driver != NULL ) { // If driver exists, it has an instance
                int instlen = strlen( net->driver->refinst->name );
                if( instlen > instFWidth ) instFWidth = instlen;
                int pinlen = strlen( net->driver->refpin->name );
                if( pinlen > pinFWidth ) pinFWidth = pinlen;
            }
        }
        // Find max length of receiving inst name
        if( curr->receiver->refinst != NULL ) {
            int instlen = strlen(curr->receiver->refinst->name);
            if( instlen > recvFWidth ) recvFWidth = instlen;
        }
        // Do the first pointer reversal
        btptr tmp = curr->next;
        curr->next = prev;
        prev = curr;
        curr = tmp;
    }

    curr = prev;
    prev = NULL;

    while( curr != NULL ) {
        print_path_component( netFWidth, instFWidth, pinFWidth, recvFWidth, curr );
        // Do the second pointer reversal
        btptr tmp = curr->next;
        curr->next = prev;
        prev = curr;
        curr = tmp;
    }
    fprintf(stdout, "\n");
}

/*--------------------------------------------------------------*/
/* Main program                                                 */
/*--------------------------------------------------------------*/

int
main(int objc, char *argv[])
{
    FILE *flib;
    FILE *fsrc;
    FILE *fdly;
    double period = 0.0;
    double outLoad = 0.0;
    double inTrans = 0.0;
    char *delayfile = NULL;
    int ival, firstarg = 1;
    int longFormat = 0;    // Is the long format option present

    // Liberty database

    lutable *tables = NULL;
    cell *cells = NULL;
    lutable *scalar;

    // Verilog netlist database

    instptr     instlist = NULL;
    netptr      netlist = NULL;
    connlistptr clockconnlist = NULL;
    connlistptr newinputconn, inputconnlist = NULL;
    connptr     testconn, inputlist = NULL;
    connptr     outputlist = NULL;

    // Timing path database
    ddataptr    pathlist = NULL;
    ddataptr    freeddata, testddata, *orderedpaths;
    btptr       freebt, testbt;
    int         numpaths, numterms, i;
    char        badtiming;
    double      slack;

    verbose = 0;
    exhaustive = 0;

    while ((firstarg < objc) && (*argv[firstarg] == '-')) {
       if (!strcmp(argv[firstarg], "-d") || !strcmp(argv[firstarg], "--delay")) {
          delayfile = strdup(argv[firstarg + 1]);
          firstarg += 2;
       }
       else if (!strcmp(argv[firstarg], "-p") || !strcmp(argv[firstarg], "--period")) {
          period = strtod(argv[firstarg + 1], NULL);
          firstarg += 2;
       }
       else if (!strcmp(argv[firstarg], "-l") || !strcmp(argv[firstarg], "--load")) {
          outLoad = strtod(argv[firstarg + 1], NULL);
          firstarg += 2;
       }
       else if (!strcmp(argv[firstarg], "-t") || !strcmp(argv[firstarg], "--trans")) {
          inTrans = strtod(argv[firstarg + 1], NULL);
          firstarg += 2;
       }
       else if (!strcmp(argv[firstarg], "-L") || !strcmp(argv[firstarg], "--long")) {
          longFormat = 1;
          firstarg++;
       }
       else if (!strcmp(argv[firstarg], "-v") || !strcmp(argv[firstarg], "--verbose")) {
          sscanf(argv[firstarg + 1], "%d", &ival);
          verbose = (unsigned char)ival;
          firstarg += 2;
       }
       else if (!strcmp(argv[firstarg], "-e") || !strcmp(argv[firstarg], "--exhaustive")) {
          exhaustive = 1;
          firstarg++;
       }
       else if (!strcmp(argv[firstarg], "-V") || !strcmp(argv[firstarg], "--version")) {
          fprintf(stderr, "Vesta Static Timing Analyzer version 0.3\n");
          exit(0);
       }
       else {
          fprintf(stderr, "Unknown option \"%s\"\n", argv[firstarg]);
          firstarg++;
       }
    }

    if (objc - firstarg != 2) {
        fprintf(stderr, "Usage:  vesta [options] <name.v> <name.lib>\n");
        fprintf(stderr, "Options:\n");
        fprintf(stderr, "--delay <delay_file>   or      -d <delay_file>\n");
        fprintf(stderr, "--period <period>      or      -p <period>\n");
        fprintf(stderr, "--load <load>          or      -l <load>\n");
        fprintf(stderr, "--long                 or      -L\n");
        fprintf(stderr, "--verbose <level>      or      -v <level>\n");
        fprintf(stderr, "--exhaustive           or      -e\n");
        fprintf(stderr, "--version              or      -V\n");
        exit (1);
    }
    else {
        fflush(stdout);
        fprintf(stdout, "----------------------------------------------\n");
        fprintf(stdout, "Vesta static timing analysis tool\n");
        fprintf(stdout, "(c) 2013-2017 Tim Edwards, Open Circuit Design\n");
        fprintf(stdout, "----------------------------------------------\n\n");
        fflush(stdout);
    }

    fsrc = fopen(argv[firstarg], "r");
    if (fsrc == NULL) {
        fprintf(stderr, "Cannot open %s for reading\n", argv[firstarg]);
        exit (1);
    }

    flib = fopen(argv[firstarg + 1], "r");
    if (flib == NULL) {
        fprintf(stderr, "Cannot open %s for reading\n", argv[firstarg + 1]);
        exit (1);
    }

    /*------------------------------------------------------------------*/
    /* Generate one table template for the "scalar" case                */
    /*------------------------------------------------------------------*/

    scalar = (lutable *)malloc(sizeof(lutable));
    scalar->name = strdup("scalar");
    scalar->invert = 0;
    scalar->var1 = CONSTRAINED_TIME;
    scalar->var2 = OUTPUT_CAP;
    scalar->size1 = 1;
    scalar->size2 = 1;
    scalar->idx1.times = (double *)malloc(sizeof(double));
    scalar->idx2.caps = (double *)malloc(sizeof(double));
    scalar->values = (double *)malloc(sizeof(double));

    scalar->idx1.times[0] = 0.0;
    scalar->idx2.caps[0] = 0.0;
    scalar->values[0] = 0.0;

    scalar->next = NULL;
    tables = scalar;

    /*------------------------------------------------------------------*/
    /* Read the liberty format file.  This is not a rigorous parser!    */
    /*------------------------------------------------------------------*/

    fileCurrentLine = 0;
    libertyRead(flib, &tables, &cells);
    fflush(stdout);
    fprintf(stdout, "Lib Read:  Processed %d lines.\n", fileCurrentLine);
    if (flib != NULL) fclose(flib);

    /*--------------------------------------------------*/
    /* Debug:  Print summary of liberty database        */
    /*--------------------------------------------------*/

    if (verbose > 2) {

        lutable *newtable;
        cell *newcell;
        pin *newpin;

        for (newtable = tables; newtable; newtable = newtable->next) {
            fprintf(stdout, "Table: %s\n", newtable->name);
        }

        for (newcell = cells; newcell; newcell = newcell->next) {
            fprintf(stdout, "Cell: %s\n", newcell->name);
            fprintf(stdout, "   Function: %s\n", newcell->function);
            for (newpin = newcell->pins; newpin; newpin = newpin->next) {
                if (newpin->type == INPUT)
                    fprintf(stdout, "   Pin: %s  cap=%g\n", newpin->name, newpin->capr);
            }
            fprintf(stdout, "\n");
        }
    }

    /*------------------------------------------------------------------*/
    /* Read verilog netlist.  This is also not a rigorous parser!       */
    /*------------------------------------------------------------------*/

    struct hashlist *Nethash[OBJHASHSIZE];

    /* See hash.c for these routines and variables */
    hashfunc = hash;
    matchfunc = match;

    /* Initialize net hash table */
    InitializeHashTable(Nethash);

    fileCurrentLine = 0;

    verilogRead(fsrc, cells, &netlist, &instlist, &inputlist, &outputlist, Nethash);

    if (delayfile != NULL) {
        fdly = fopen(delayfile, "r");

        if (fdly == NULL) {
            fprintf(stderr, "Cannot open %s for reading\n", delayfile);
            exit (1);
        }
    }
    else
	fdly = NULL;

    fflush(stdout);
    fprintf(stdout, "Verilog netlist read:  Processed %d lines.\n", fileCurrentLine);
    if (fsrc != NULL) fclose(fsrc);

    /*--------------------------------------------------*/
    /* Debug:  Print summary of verilog source          */
    /*--------------------------------------------------*/

    if (verbose > 1) {
        connect *testoutput;
        connect *testinput;
        net *testnet;
        instance *testinst;

        for (testinput = inputlist; testinput; testinput = testinput->next) {
            if (testinput->refnet)
                fprintf(stdout, "   Input: %s\n", testinput->refnet->name);
        }
        for (testoutput = outputlist; testoutput; testoutput = testoutput->next) {
            if (testoutput->refnet)
                fprintf(stdout, "   Output: %s\n", testoutput->refnet->name);
        }
        for (testnet = netlist; testnet; testnet = testnet->next) {
            fprintf(stdout, "   Net: %s\n", testnet->name);
        }
        for (testinst = instlist; testinst; testinst = testinst->next) {
            fprintf(stdout, "   Gate: %s\n", testinst->name);
        }
    }

    /*--------------------------------------------------*/
    /* Generate internal links representing the network */
    /*--------------------------------------------------*/

    createLinks(netlist, instlist, inputlist, outputlist);

    /* Generate a connection list from inputlist */

    for (testconn = inputlist; testconn; testconn = testconn->next) {
        newinputconn = (connlistptr)malloc(sizeof(connlist));
        newinputconn->connection = testconn;
        newinputconn->next = inputconnlist;
        inputconnlist = newinputconn;
    }

    /*--------------------------------------------------*/
    /* Assign net types, mainly to identify clocks      */
    /* Return a list of clock nets                      */
    /*--------------------------------------------------*/

    numterms = assign_net_types(netlist, &clockconnlist);

    if (verbose > 1)
        fprintf(stdout, "Number of terminals to check: %d\n", numterms);

    /*--------------------------------------------------*/
    /* Calculate total load on each net                 */
    /* To do:  Add wire models or computed wire delays  */
    /*--------------------------------------------------*/

    if (fdly != NULL) {
        delayRead(fdly, Nethash);
        fclose(fdly);
    }

    /* Hash table no longer needed */
    HashKill(Nethash);

    computeLoads(netlist, instlist, outLoad);

    /*--------------------------------------------------*/
    /* Identify all clock-to-terminal paths             */
    /*--------------------------------------------------*/

    numpaths = find_clock_to_term_paths(clockconnlist, &pathlist, netlist, MAXIMUM_TIME);
    fprintf(stdout, "Number of paths analyzed:  %d\n", numpaths);

    /*--------------------------------------------------*/
    /* Collect paths into a non-linked array so that    */
    /* they can be sorted by delay time                 */
    /*--------------------------------------------------*/

    orderedpaths = (ddataptr *)malloc(numpaths * sizeof(ddataptr));

    i = 0;
    for (testddata = pathlist; testddata; testddata = testddata->next) {
       orderedpaths[i] = testddata;
       i++;
    }

    qsort(orderedpaths, numpaths, sizeof(ddataptr), (__compar_fn_t)compdelay);

    /*--------------------------------------------------*/
    /* Report on top 20 maximum delay paths             */
    /*--------------------------------------------------*/

    fprintf(stdout, "\nTop %d maximum delay paths:\n", (numpaths >= 20) ? 20 : numpaths);
    badtiming = 0;
    for (i = 0; ((i < 20) && (i < numpaths)); i++) {
        testddata = orderedpaths[i];
        for (testbt = testddata->backtrace; testbt->next; testbt = testbt->next);

        if (testddata->backtrace->receiver->refinst != NULL) {
            fprintf(stdout, "Path %s/%s to %s/%s delay %g ps",
                        testbt->receiver->refinst->name,
                        testbt->receiver->refpin->name,
                        testddata->backtrace->receiver->refinst->name,
                        testddata->backtrace->receiver->refpin->name,
                        testddata->delay);
        }
        else {
            fprintf(stdout, "Path %s/%s to output pin %s delay %g ps",
                        testbt->receiver->refinst->name,
                        testbt->receiver->refpin->name,
                        testddata->backtrace->receiver->refnet->name,
                        testddata->delay);
        }

        if (period > 0.0) {
            slack = period - testddata->delay;
            fprintf(stdout, "   Slack = %g ps", slack);
            if (slack < 0.0) badtiming = 1;
        }
        fprintf(stdout, "\n");
        if( longFormat ) print_path(testddata->backtrace);
    }

    if (period > 0.0) {
        if (badtiming) {
            fprintf(stdout, "ERROR:  Design fails timing requirements.\n");
        }
        else {
            fprintf(stdout, "Design meets timing requirements.\n");
        }
    }
    else if (orderedpaths[0] != NULL) {
        fprintf(stdout, "Computed maximum clock frequency (zero slack) = %g MHz\n",
                (1.0E6 / orderedpaths[0]->delay));
    }
    fprintf(stdout, "-----------------------------------------\n\n");
    fflush(stdout);

    /*--------------------------------------------------*/
    /* Clean up the path list                           */
    /*--------------------------------------------------*/

    while (pathlist != NULL) {
        freeddata = pathlist;
        pathlist = pathlist->next;
        while (freeddata->backtrace != NULL) {
            freebt = freeddata->backtrace;
            freeddata->backtrace = freeddata->backtrace->next;
            freebt->refcnt--;
            if (freebt->refcnt == 0) free(freebt);
        }
        free(freeddata);
    }

    free(orderedpaths);

    /*--------------------------------------------------*/
    /* Now calculate minimum delay paths                */
    /*--------------------------------------------------*/

    numpaths = find_clock_to_term_paths(clockconnlist, &pathlist, netlist, MINIMUM_TIME);
    fprintf(stdout, "Number of paths analyzed:  %d\n", numpaths);

    /*--------------------------------------------------*/
    /* Collect paths into a non-linked array so that    */
    /* they can be sorted by delay time                 */
    /*--------------------------------------------------*/

    orderedpaths = (ddataptr *)malloc(numpaths * sizeof(ddataptr));

    i = 0;
    for (testddata = pathlist; testddata; testddata = testddata->next) {
       orderedpaths[i] = testddata;
       i++;
    }

    qsort(orderedpaths, numpaths, sizeof(ddataptr), (__compar_fn_t)compdelay);

    /*--------------------------------------------------*/
    /* Report on top 20 minimum delay paths             */
    /*--------------------------------------------------*/

    fprintf(stdout, "\nTop %d minimum delay paths:\n", (numpaths >= 20) ? 20 : numpaths);
    badtiming = 0;
    for (i = numpaths; (i > (numpaths - 20)) && (i > 0); i--) {
        testddata = orderedpaths[i - 1];
        for (testbt = testddata->backtrace; testbt->next; testbt = testbt->next);

        if (testddata->backtrace->receiver->refinst != NULL) {
            fprintf(stdout, "Path %s/%s to %s/%s delay %g ps\n",
                        testbt->receiver->refinst->name,
                        testbt->receiver->refpin->name,
                        testddata->backtrace->receiver->refinst->name,
                        testddata->backtrace->receiver->refpin->name,
                        testddata->delay);
        }
        else {
            fprintf(stdout, "Path %s/%s to output pin %s delay %g ps\n",
                        testbt->receiver->refinst->name,
                        testbt->receiver->refpin->name,
                        testddata->backtrace->receiver->refnet->name,
                        testddata->delay);
        }
        if( longFormat ) print_path(testddata->backtrace);

        if (testddata->delay < 0.0) badtiming = 1;
    }
    if (badtiming)
        fprintf(stdout, "ERROR:  Design fails minimum hold timing.\n");
    else
        fprintf(stdout, "Design meets minimum hold timing.\n");

    fprintf(stdout, "-----------------------------------------\n\n");
    fflush(stdout);

    /*--------------------------------------------------*/
    /* Clean up the path list                           */
    /*--------------------------------------------------*/

    while (pathlist != NULL) {
        freeddata = pathlist;
        pathlist = pathlist->next;
        while (freeddata->backtrace != NULL) {
            freebt = freeddata->backtrace;
            freeddata->backtrace = freeddata->backtrace->next;
            freebt->refcnt--;
            if (freebt->refcnt == 0) free(freebt);
        }
        free(freeddata);
    }

    free(orderedpaths);

    for (testconn = inputlist; testconn; testconn = testconn->next) {
        testconn->tag = NULL;
        testconn->metric = -1;
    }

    /*--------------------------------------------------*/
    /* Identify all input-to-terminal paths             */
    /*--------------------------------------------------*/

    numpaths = find_clock_to_term_paths(inputconnlist, &pathlist, netlist, MAXIMUM_TIME);
    fprintf(stdout, "Number of paths analyzed:  %d\n", numpaths);

    /*--------------------------------------------------*/
    /* Collect paths into a non-linked array so that    */
    /* they can be sorted by delay time                 */
    /*--------------------------------------------------*/

    orderedpaths = (ddataptr *)malloc(numpaths * sizeof(ddataptr));

    i = 0;
    for (testddata = pathlist; testddata; testddata = testddata->next) {
       orderedpaths[i] = testddata;
       i++;
    }

    qsort(orderedpaths, numpaths, sizeof(ddataptr), (__compar_fn_t)compdelay);

    /*--------------------------------------------------*/
    /* Report on top 20 maximum delay paths             */
    /*--------------------------------------------------*/

    fprintf(stdout, "\nTop %d maximum delay paths:\n", (numpaths >= 20) ? 20 : numpaths);
    for (i = 0; ((i < 20) && (i < numpaths)); i++) {
        testddata = orderedpaths[i];
        for (testbt = testddata->backtrace; testbt->next; testbt = testbt->next);

        if (testddata->backtrace->receiver->refinst != NULL) {
            fprintf(stdout, "Path input pin %s to %s/%s delay %g ps\n",
                        testbt->receiver->refnet->name,
                        testddata->backtrace->receiver->refinst->name,
                        testddata->backtrace->receiver->refpin->name,
                        testddata->delay);
        }
        else {
            fprintf(stdout, "Path input pin %s to output pin %s delay %g ps\n",
                        testbt->receiver->refnet->name,
                        testddata->backtrace->receiver->refnet->name,
                        testddata->delay);
        }
        if( longFormat ) print_path(testddata->backtrace);
    }

    fprintf(stdout, "-----------------------------------------\n\n");
    fflush(stdout);

    /*--------------------------------------------------*/
    /* Clean up the path list                           */
    /*--------------------------------------------------*/

    while (pathlist != NULL) {
        freeddata = pathlist;
        pathlist = pathlist->next;
        while (freeddata->backtrace != NULL) {
            freebt = freeddata->backtrace;
            freeddata->backtrace = freeddata->backtrace->next;
            freebt->refcnt--;
            if (freebt->refcnt == 0) free(freebt);
        }
        free(freeddata);
    }

    free(orderedpaths);

    for (testconn = inputlist; testconn; testconn = testconn->next) {
        testconn->tag = NULL;
        testconn->metric = 1E50;
    }

    /*--------------------------------------------------*/
    /* Now calculate minimum delay paths from inputs    */
    /*--------------------------------------------------*/

    numpaths = find_clock_to_term_paths(inputconnlist, &pathlist, netlist, MINIMUM_TIME);
    fprintf(stdout, "Number of paths analyzed:  %d\n", numpaths);

    /*--------------------------------------------------*/
    /* Collect paths into a non-linked array so that    */
    /* they can be sorted by delay time                 */
    /*--------------------------------------------------*/

    orderedpaths = (ddataptr *)malloc(numpaths * sizeof(ddataptr));

    i = 0;
    for (testddata = pathlist; testddata; testddata = testddata->next) {
       orderedpaths[i] = testddata;
       i++;
    }

    qsort(orderedpaths, numpaths, sizeof(ddataptr), (__compar_fn_t)compdelay);

    /*--------------------------------------------------*/
    /* Report on top 20 minimum delay paths             */
    /*--------------------------------------------------*/

    fprintf(stdout, "\nTop %d minimum delay paths:\n", (numpaths >= 20) ? 20 : numpaths);
    for (i = numpaths; (i > (numpaths - 20)) && (i > 0); i--) {
        testddata = orderedpaths[i - 1];
        for (testbt = testddata->backtrace; testbt->next; testbt = testbt->next);

        if (testddata->backtrace->receiver->refinst != NULL) {
            fprintf(stdout, "Path input pin %s to %s/%s delay %g ps\n",
                        testbt->receiver->refnet->name,
                        testddata->backtrace->receiver->refinst->name,
                        testddata->backtrace->receiver->refpin->name,
                        testddata->delay);
        }
        else {
            fprintf(stdout, "Path input pin %s to output pin %s delay %g ps\n",
                        testbt->receiver->refnet->name,
                        testddata->backtrace->receiver->refnet->name,
                        testddata->delay);
        }
        if( longFormat ) print_path(testddata->backtrace);
    }

    fprintf(stdout, "-----------------------------------------\n\n");
    fflush(stdout);

    /*--------------------------------------------------*/
    /* Clean up the path list                           */
    /*--------------------------------------------------*/

    while (pathlist != NULL) {
        freeddata = pathlist;
        pathlist = pathlist->next;
        while (freeddata->backtrace != NULL) {
            freebt = freeddata->backtrace;
            freeddata->backtrace = freeddata->backtrace->next;
            freebt->refcnt--;
            if (freebt->refcnt == 0) free(freebt);
        }
        free(freeddata);
    }

    free(orderedpaths);

    return 0;
}
